Thomas Vachuska
Committed by Brian O'Connor

IntentManager refactoring for flow objectives.

Change-Id: I220682dbace03908b25ba86332664238dafaef90
...@@ -16,23 +16,32 @@ ...@@ -16,23 +16,32 @@
16 16
17 package org.onosproject.net.intent.impl; 17 package org.onosproject.net.intent.impl;
18 18
19 +import com.google.common.collect.Sets;
20 +import org.onosproject.net.DeviceId;
19 import org.onosproject.net.flow.FlowRule; 21 import org.onosproject.net.flow.FlowRule;
20 import org.onosproject.net.flow.FlowRuleOperations; 22 import org.onosproject.net.flow.FlowRuleOperations;
21 import org.onosproject.net.flow.FlowRuleOperationsContext; 23 import org.onosproject.net.flow.FlowRuleOperationsContext;
22 import org.onosproject.net.flow.FlowRuleService; 24 import org.onosproject.net.flow.FlowRuleService;
23 import org.onosproject.net.flowobjective.FlowObjectiveService; 25 import org.onosproject.net.flowobjective.FlowObjectiveService;
26 +import org.onosproject.net.flowobjective.Objective;
27 +import org.onosproject.net.flowobjective.ObjectiveContext;
28 +import org.onosproject.net.flowobjective.ObjectiveError;
29 +import org.onosproject.net.intent.FlowObjectiveIntent;
24 import org.onosproject.net.intent.FlowRuleIntent; 30 import org.onosproject.net.intent.FlowRuleIntent;
25 import org.onosproject.net.intent.Intent; 31 import org.onosproject.net.intent.Intent;
26 import org.onosproject.net.intent.IntentData; 32 import org.onosproject.net.intent.IntentData;
27 import org.onosproject.net.intent.IntentStore; 33 import org.onosproject.net.intent.IntentStore;
28 import org.slf4j.Logger; 34 import org.slf4j.Logger;
29 35
36 +import java.util.ArrayList;
30 import java.util.Collection; 37 import java.util.Collection;
31 import java.util.List; 38 import java.util.List;
32 import java.util.Optional; 39 import java.util.Optional;
33 import java.util.Set; 40 import java.util.Set;
41 +import java.util.function.Consumer;
34 import java.util.stream.Collectors; 42 import java.util.stream.Collectors;
35 43
44 +import static com.google.common.base.Preconditions.checkState;
36 import static org.onosproject.net.intent.IntentState.*; 45 import static org.onosproject.net.intent.IntentState.*;
37 import static org.slf4j.LoggerFactory.getLogger; 46 import static org.slf4j.LoggerFactory.getLogger;
38 47
...@@ -69,51 +78,10 @@ class IntentInstaller { ...@@ -69,51 +78,10 @@ class IntentInstaller {
69 this.flowObjectiveService = flowObjectiveService; 78 this.flowObjectiveService = flowObjectiveService;
70 } 79 }
71 80
72 - private void applyIntentData(Optional<IntentData> intentData,
73 - FlowRuleOperations.Builder builder,
74 - Direction direction) {
75 - if (!intentData.isPresent()) {
76 - return;
77 - }
78 - IntentData data = intentData.get();
79 -
80 - List<Intent> intentsToApply = data.installables();
81 - if (!intentsToApply.stream().allMatch(x -> x instanceof FlowRuleIntent)) {
82 - throw new IllegalStateException("installable intents must be FlowRuleIntent");
83 - }
84 -
85 - if (direction == Direction.ADD) {
86 - trackerService.addTrackedResources(data.key(), data.intent().resources());
87 - intentsToApply.forEach(installable ->
88 - trackerService.addTrackedResources(data.key(), installable.resources()));
89 - } else {
90 - trackerService.removeTrackedResources(data.key(), data.intent().resources());
91 - intentsToApply.forEach(installable ->
92 - trackerService.removeTrackedResources(data.intent().key(),
93 - installable.resources()));
94 - }
95 -
96 - // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
97 - builder.newStage();
98 -
99 - List<Collection<FlowRule>> stages = intentsToApply.stream()
100 - .map(x -> (FlowRuleIntent) x)
101 - .map(FlowRuleIntent::flowRules)
102 - .collect(Collectors.toList());
103 -
104 - for (Collection<FlowRule> rules : stages) {
105 - if (direction == Direction.ADD) {
106 - rules.forEach(builder::add);
107 - } else {
108 - rules.forEach(builder::remove);
109 - }
110 - }
111 -
112 - }
113 81
114 // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents 82 // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
115 - // Note: Intent Manager should have never become dependent on a specific 83 + // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
116 - // intent type. 84 + // This will be addressed in intent domains work; not now.
117 85
118 /** 86 /**
119 * Applies the specified intent updates to the environment by uninstalling 87 * Applies the specified intent updates to the environment by uninstalling
...@@ -123,15 +91,8 @@ class IntentInstaller { ...@@ -123,15 +91,8 @@ class IntentInstaller {
123 * @param toInstall optional intent to install 91 * @param toInstall optional intent to install
124 */ 92 */
125 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) { 93 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
126 - // need to consider if FlowRuleIntent is only one as installable intent or not 94 + // Hook for handling success
127 - 95 + Consumer<OperationContext> successConsumer = (ctx) -> {
128 - FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
129 - applyIntentData(toUninstall, builder, Direction.REMOVE);
130 - applyIntentData(toInstall, builder, Direction.ADD);
131 -
132 - FlowRuleOperations operations = builder.build(new FlowRuleOperationsContext() {
133 - @Override
134 - public void onSuccess(FlowRuleOperations ops) {
135 if (toInstall.isPresent()) { 96 if (toInstall.isPresent()) {
136 IntentData installData = toInstall.get(); 97 IntentData installData = toInstall.get();
137 log.debug("Completed installing: {}", installData.key()); 98 log.debug("Completed installing: {}", installData.key());
...@@ -151,15 +112,15 @@ class IntentInstaller { ...@@ -151,15 +112,15 @@ class IntentInstaller {
151 } 112 }
152 store.write(uninstallData); 113 store.write(uninstallData);
153 } 114 }
154 - } 115 + };
155 116
156 - @Override 117 + // Hook for handling errors
157 - public void onError(FlowRuleOperations ops) { 118 + Consumer<OperationContext> errorConsumer = (ctx) -> {
158 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT) 119 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
159 if (toInstall.isPresent()) { 120 if (toInstall.isPresent()) {
160 IntentData installData = toInstall.get(); 121 IntentData installData = toInstall.get();
161 log.warn("Failed installation: {} {} on {}", 122 log.warn("Failed installation: {} {} on {}",
162 - installData.key(), installData.intent(), ops); 123 + installData.key(), installData.intent(), ctx.error());
163 installData.setState(CORRUPT); 124 installData.setState(CORRUPT);
164 installData.incrementErrorCount(); 125 installData.incrementErrorCount();
165 store.write(installData); 126 store.write(installData);
...@@ -168,13 +129,136 @@ class IntentInstaller { ...@@ -168,13 +129,136 @@ class IntentInstaller {
168 if (toUninstall.isPresent()) { 129 if (toUninstall.isPresent()) {
169 IntentData uninstallData = toUninstall.get(); 130 IntentData uninstallData = toUninstall.get();
170 log.warn("Failed withdrawal: {} {} on {}", 131 log.warn("Failed withdrawal: {} {} on {}",
171 - uninstallData.key(), uninstallData.intent(), ops); 132 + uninstallData.key(), uninstallData.intent(), ctx.error());
172 uninstallData.setState(CORRUPT); 133 uninstallData.setState(CORRUPT);
173 uninstallData.incrementErrorCount(); 134 uninstallData.incrementErrorCount();
174 store.write(uninstallData); 135 store.write(uninstallData);
175 } 136 }
137 + };
138 +
139 + // Create a context for tracking the backing operations for applying
140 + // the intents to the environment.
141 + OperationContext context = createContext(toUninstall, toInstall);
142 +
143 + context.prepare(toUninstall, toInstall, successConsumer, errorConsumer);
144 + context.apply();
145 + }
146 +
147 + // ------ Utilities to support FlowRule vs. FlowObjective behavior -------
148 +
149 + // Creates the context appropriate for tracking operations of the
150 + // the specified intents.
151 + private OperationContext createContext(Optional<IntentData> toUninstall,
152 + Optional<IntentData> toInstall) {
153 + if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
154 + return new FlowRuleOperationContext();
155 + }
156 + if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
157 + return new FlowObjectiveOperationContext();
158 + }
159 + return new ErrorContext();
160 + }
161 +
162 + private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
163 + Class<? extends Intent> intentClass) {
164 + boolean notBothNull = false;
165 + if (toInstall.isPresent()) {
166 + notBothNull = true;
167 + if (!toInstall.get().installables().stream()
168 + .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
169 + return false;
170 + }
171 + }
172 + if (toUninstall.isPresent()) {
173 + notBothNull = true;
174 + if (!toUninstall.get().installables().stream()
175 + .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
176 + return false;
177 + }
178 + }
179 + return notBothNull;
180 + }
181 +
182 + // Base context for applying and tracking operations related to installable intents.
183 + private abstract class OperationContext {
184 + protected Optional<IntentData> toUninstall;
185 + protected Optional<IntentData> toInstall;
186 + protected Consumer<OperationContext> successConsumer;
187 + protected Consumer<OperationContext> errorConsumer;
188 +
189 + abstract void apply();
190 +
191 + abstract Object error();
192 +
193 + abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
194 +
195 + void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
196 + Consumer<OperationContext> successConsumer,
197 + Consumer<OperationContext> errorConsumer) {
198 + this.toUninstall = toUninstall;
199 + this.toInstall = toInstall;
200 + this.successConsumer = successConsumer;
201 + this.errorConsumer = errorConsumer;
202 + prepareIntentData(toUninstall, Direction.REMOVE);
203 + prepareIntentData(toInstall, Direction.ADD);
204 + }
205 +
206 + /**
207 + * Applies the specified intent data, if present, to the network using the
208 + * specified context.
209 + *
210 + * @param intentData optional intent data; no-op if not present
211 + * @param direction indicates adding or removal
212 + */
213 + private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
214 + if (!intentData.isPresent()) {
215 + return;
216 + }
217 +
218 + IntentData data = intentData.get();
219 + List<Intent> intentsToApply = data.installables();
220 + checkState(intentsToApply.stream().allMatch(this::isSupported),
221 + "Unsupported installable intents detected");
222 +
223 + if (direction == Direction.ADD) {
224 + trackerService.addTrackedResources(data.key(), data.intent().resources());
225 + intentsToApply.forEach(installable ->
226 + trackerService.addTrackedResources(data.key(),
227 + installable.resources()));
228 + } else {
229 + trackerService.removeTrackedResources(data.key(), data.intent().resources());
230 + intentsToApply.forEach(installable ->
231 + trackerService.removeTrackedResources(data.intent().key(),
232 + installable.resources()));
233 + }
234 +
235 + prepareIntents(intentsToApply, direction);
236 + }
237 +
238 + private boolean isSupported(Intent intent) {
239 + return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
240 + }
241 + }
242 +
243 +
244 + // Context for applying and tracking operations related to flow rule intent.
245 + private class FlowRuleOperationContext extends OperationContext {
246 + FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
247 + FlowRuleOperationsContext flowRuleOperationsContext;
248 +
249 + void apply() {
250 + flowRuleOperationsContext = new FlowRuleOperationsContext() {
251 + @Override
252 + public void onSuccess(FlowRuleOperations ops) {
253 + successConsumer.accept(FlowRuleOperationContext.this);
176 } 254 }
177 - }); 255 +
256 + @Override
257 + public void onError(FlowRuleOperations ops) {
258 + errorConsumer.accept(FlowRuleOperationContext.this);
259 + }
260 + };
261 + FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
178 262
179 if (log.isTraceEnabled()) { 263 if (log.isTraceEnabled()) {
180 log.trace("applying intent {} -> {} with {} rules: {}", 264 log.trace("applying intent {} -> {} with {} rules: {}",
...@@ -186,4 +270,140 @@ class IntentInstaller { ...@@ -186,4 +270,140 @@ class IntentInstaller {
186 270
187 flowRuleService.apply(operations); 271 flowRuleService.apply(operations);
188 } 272 }
273 +
274 + @Override
275 + public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
276 + // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
277 + builder.newStage();
278 +
279 + List<Collection<FlowRule>> stages = intentsToApply.stream()
280 + .map(x -> (FlowRuleIntent) x)
281 + .map(FlowRuleIntent::flowRules)
282 + .collect(Collectors.toList());
283 +
284 + for (Collection<FlowRule> rules : stages) {
285 + if (direction == Direction.ADD) {
286 + rules.forEach(builder::add);
287 + } else {
288 + rules.forEach(builder::remove);
289 + }
290 + }
291 +
292 + }
293 +
294 + @Override
295 + public Object error() {
296 + return flowRuleOperationsContext;
297 + }
298 + }
299 +
300 + // Context for applying and tracking operations related to flow objective intents.
301 + private class FlowObjectiveOperationContext extends OperationContext {
302 + List<FlowObjectiveInstallationContext> contexts;
303 + final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
304 + final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
305 +
306 + @Override
307 + public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
308 + contexts = intentsToApply.stream()
309 + .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
310 + .collect(Collectors.toList());
311 + }
312 +
313 + // Builds the specified objective in the appropriate direction
314 + private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
315 + Direction direction) {
316 + int size = intent.objectives().size();
317 + List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
318 + for (int i = 0; i < size; i++) {
319 + DeviceId deviceId = intent.devices().get(i);
320 + Objective.Builder builder = intent.objectives().get(i).copy();
321 + FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
322 +
323 + final Objective objective;
324 + switch (direction) {
325 + case ADD:
326 + objective = builder.add(context);
327 + break;
328 + case REMOVE:
329 + objective = builder.remove(context);
330 + break;
331 + default:
332 + throw new UnsupportedOperationException("Unsupported direction " + direction);
333 + }
334 + context.setObjective(objective, deviceId);
335 + contexts.add(context);
336 + }
337 + return contexts;
338 + }
339 +
340 + @Override
341 + void apply() {
342 + contexts.forEach(objectiveContext -> {
343 + pendingContexts.add(objectiveContext);
344 + flowObjectiveService.apply(objectiveContext.deviceId,
345 + objectiveContext.objective);
346 + });
347 + }
348 +
349 + @Override
350 + public Object error() {
351 + return errorContexts;
352 + }
353 +
354 + private class FlowObjectiveInstallationContext implements ObjectiveContext {
355 + Objective objective;
356 + DeviceId deviceId;
357 +
358 + void setObjective(Objective objective, DeviceId deviceId) {
359 + this.objective = objective;
360 + this.deviceId = deviceId;
361 + }
362 +
363 + @Override
364 + public void onSuccess(Objective objective) {
365 + finish();
366 + }
367 +
368 + @Override
369 + public void onError(Objective objective, ObjectiveError error) {
370 + errorContexts.add(this);
371 + finish();
372 + }
373 +
374 + private void finish() {
375 + synchronized (pendingContexts) {
376 + pendingContexts.remove(this);
377 + if (pendingContexts.isEmpty()) {
378 + if (errorContexts.isEmpty()) {
379 + successConsumer.accept(FlowObjectiveOperationContext.this);
380 + } else {
381 + errorConsumer.accept(FlowObjectiveOperationContext.this);
382 + }
383 + }
384 + }
385 + }
386 +
387 + @Override
388 + public String toString() {
389 + return String.format("(%s, %s)", deviceId, objective);
390 + }
391 + }
392 + }
393 +
394 + private class ErrorContext extends OperationContext {
395 + @Override
396 + void apply() {
397 + throw new UnsupportedOperationException("Unsupported installable intent");
398 + }
399 +
400 + @Override
401 + Object error() {
402 + return null;
403 + }
404 +
405 + @Override
406 + void prepareIntents(List<Intent> intentsToApply, Direction direction) {
407 + }
408 + }
189 } 409 }
......