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,121 +78,332 @@ class IntentInstaller { ...@@ -69,121 +78,332 @@ class IntentInstaller {
69 this.flowObjectiveService = flowObjectiveService; 78 this.flowObjectiveService = flowObjectiveService;
70 } 79 }
71 80
72 - private void applyIntentData(Optional<IntentData> intentData, 81 +
73 - FlowRuleOperations.Builder builder, 82 + // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
74 - Direction direction) { 83 + // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
75 - if (!intentData.isPresent()) { 84 + // This will be addressed in intent domains work; not now.
76 - return; 85 +
86 + /**
87 + * Applies the specified intent updates to the environment by uninstalling
88 + * and installing the intents and updating the store references appropriately.
89 + *
90 + * @param toUninstall optional intent to uninstall
91 + * @param toInstall optional intent to install
92 + */
93 + void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
94 + // Hook for handling success
95 + Consumer<OperationContext> successConsumer = (ctx) -> {
96 + if (toInstall.isPresent()) {
97 + IntentData installData = toInstall.get();
98 + log.debug("Completed installing: {}", installData.key());
99 + installData.setState(INSTALLED);
100 + store.write(installData);
101 + } else if (toUninstall.isPresent()) {
102 + IntentData uninstallData = toUninstall.get();
103 + log.debug("Completed withdrawing: {}", uninstallData.key());
104 + switch (uninstallData.request()) {
105 + case INSTALL_REQ:
106 + uninstallData.setState(FAILED);
107 + break;
108 + case WITHDRAW_REQ:
109 + default: //TODO "default" case should not happen
110 + uninstallData.setState(WITHDRAWN);
111 + break;
112 + }
113 + store.write(uninstallData);
114 + }
115 + };
116 +
117 + // Hook for handling errors
118 + Consumer<OperationContext> errorConsumer = (ctx) -> {
119 + // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
120 + if (toInstall.isPresent()) {
121 + IntentData installData = toInstall.get();
122 + log.warn("Failed installation: {} {} on {}",
123 + installData.key(), installData.intent(), ctx.error());
124 + installData.setState(CORRUPT);
125 + installData.incrementErrorCount();
126 + store.write(installData);
127 + }
128 + // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
129 + if (toUninstall.isPresent()) {
130 + IntentData uninstallData = toUninstall.get();
131 + log.warn("Failed withdrawal: {} {} on {}",
132 + uninstallData.key(), uninstallData.intent(), ctx.error());
133 + uninstallData.setState(CORRUPT);
134 + uninstallData.incrementErrorCount();
135 + store.write(uninstallData);
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();
77 } 155 }
78 - IntentData data = intentData.get(); 156 + if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
157 + return new FlowObjectiveOperationContext();
158 + }
159 + return new ErrorContext();
160 + }
79 161
80 - List<Intent> intentsToApply = data.installables(); 162 + private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
81 - if (!intentsToApply.stream().allMatch(x -> x instanceof FlowRuleIntent)) { 163 + Class<? extends Intent> intentClass) {
82 - throw new IllegalStateException("installable intents must be FlowRuleIntent"); 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 + }
83 } 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();
84 190
85 - if (direction == Direction.ADD) { 191 + abstract Object error();
86 - trackerService.addTrackedResources(data.key(), data.intent().resources()); 192 +
87 - intentsToApply.forEach(installable -> 193 + abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
88 - trackerService.addTrackedResources(data.key(), installable.resources())); 194 +
89 - } else { 195 + void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
90 - trackerService.removeTrackedResources(data.key(), data.intent().resources()); 196 + Consumer<OperationContext> successConsumer,
91 - intentsToApply.forEach(installable -> 197 + Consumer<OperationContext> errorConsumer) {
92 - trackerService.removeTrackedResources(data.intent().key(), 198 + this.toUninstall = toUninstall;
93 - installable.resources())); 199 + this.toInstall = toInstall;
200 + this.successConsumer = successConsumer;
201 + this.errorConsumer = errorConsumer;
202 + prepareIntentData(toUninstall, Direction.REMOVE);
203 + prepareIntentData(toInstall, Direction.ADD);
94 } 204 }
95 205
96 - // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so. 206 + /**
97 - builder.newStage(); 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 + }
98 217
99 - List<Collection<FlowRule>> stages = intentsToApply.stream() 218 + IntentData data = intentData.get();
100 - .map(x -> (FlowRuleIntent) x) 219 + List<Intent> intentsToApply = data.installables();
101 - .map(FlowRuleIntent::flowRules) 220 + checkState(intentsToApply.stream().allMatch(this::isSupported),
102 - .collect(Collectors.toList()); 221 + "Unsupported installable intents detected");
103 222
104 - for (Collection<FlowRule> rules : stages) {
105 if (direction == Direction.ADD) { 223 if (direction == Direction.ADD) {
106 - rules.forEach(builder::add); 224 + trackerService.addTrackedResources(data.key(), data.intent().resources());
225 + intentsToApply.forEach(installable ->
226 + trackerService.addTrackedResources(data.key(),
227 + installable.resources()));
107 } else { 228 } else {
108 - rules.forEach(builder::remove); 229 + trackerService.removeTrackedResources(data.key(), data.intent().resources());
230 + intentsToApply.forEach(installable ->
231 + trackerService.removeTrackedResources(data.intent().key(),
232 + installable.resources()));
109 } 233 }
234 +
235 + prepareIntents(intentsToApply, direction);
110 } 236 }
111 237
238 + private boolean isSupported(Intent intent) {
239 + return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
240 + }
112 } 241 }
113 242
114 - // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
115 - // Note: Intent Manager should have never become dependent on a specific
116 - // intent type.
117 -
118 - /**
119 - * Applies the specified intent updates to the environment by uninstalling
120 - * and installing the intents and updating the store references appropriately.
121 - *
122 - * @param toUninstall optional intent to uninstall
123 - * @param toInstall optional intent to install
124 - */
125 - void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
126 - // need to consider if FlowRuleIntent is only one as installable intent or not
127 243
244 + // Context for applying and tracking operations related to flow rule intent.
245 + private class FlowRuleOperationContext extends OperationContext {
128 FlowRuleOperations.Builder builder = FlowRuleOperations.builder(); 246 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
129 - applyIntentData(toUninstall, builder, Direction.REMOVE); 247 + FlowRuleOperationsContext flowRuleOperationsContext;
130 - applyIntentData(toInstall, builder, Direction.ADD); 248 +
249 + void apply() {
250 + flowRuleOperationsContext = new FlowRuleOperationsContext() {
251 + @Override
252 + public void onSuccess(FlowRuleOperations ops) {
253 + successConsumer.accept(FlowRuleOperationContext.this);
254 + }
255 +
256 + @Override
257 + public void onError(FlowRuleOperations ops) {
258 + errorConsumer.accept(FlowRuleOperationContext.this);
259 + }
260 + };
261 + FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
262 +
263 + if (log.isTraceEnabled()) {
264 + log.trace("applying intent {} -> {} with {} rules: {}",
265 + toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
266 + toInstall.map(x -> x.key().toString()).orElse("<empty>"),
267 + operations.stages().stream().mapToLong(Set::size).sum(),
268 + operations.stages());
269 + }
270 +
271 + flowRuleService.apply(operations);
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 + }
131 367
132 - FlowRuleOperations operations = builder.build(new FlowRuleOperationsContext() {
133 @Override 368 @Override
134 - public void onSuccess(FlowRuleOperations ops) { 369 + public void onError(Objective objective, ObjectiveError error) {
135 - if (toInstall.isPresent()) { 370 + errorContexts.add(this);
136 - IntentData installData = toInstall.get(); 371 + finish();
137 - log.debug("Completed installing: {}", installData.key()); 372 + }
138 - installData.setState(INSTALLED); 373 +
139 - store.write(installData); 374 + private void finish() {
140 - } else if (toUninstall.isPresent()) { 375 + synchronized (pendingContexts) {
141 - IntentData uninstallData = toUninstall.get(); 376 + pendingContexts.remove(this);
142 - log.debug("Completed withdrawing: {}", uninstallData.key()); 377 + if (pendingContexts.isEmpty()) {
143 - switch (uninstallData.request()) { 378 + if (errorContexts.isEmpty()) {
144 - case INSTALL_REQ: 379 + successConsumer.accept(FlowObjectiveOperationContext.this);
145 - uninstallData.setState(FAILED); 380 + } else {
146 - break; 381 + errorConsumer.accept(FlowObjectiveOperationContext.this);
147 - case WITHDRAW_REQ: 382 + }
148 - default: //TODO "default" case should not happen
149 - uninstallData.setState(WITHDRAWN);
150 - break;
151 } 383 }
152 - store.write(uninstallData);
153 } 384 }
154 } 385 }
155 386
156 @Override 387 @Override
157 - public void onError(FlowRuleOperations ops) { 388 + public String toString() {
158 - // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT) 389 + return String.format("(%s, %s)", deviceId, objective);
159 - if (toInstall.isPresent()) {
160 - IntentData installData = toInstall.get();
161 - log.warn("Failed installation: {} {} on {}",
162 - installData.key(), installData.intent(), ops);
163 - installData.setState(CORRUPT);
164 - installData.incrementErrorCount();
165 - store.write(installData);
166 - }
167 - // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
168 - if (toUninstall.isPresent()) {
169 - IntentData uninstallData = toUninstall.get();
170 - log.warn("Failed withdrawal: {} {} on {}",
171 - uninstallData.key(), uninstallData.intent(), ops);
172 - uninstallData.setState(CORRUPT);
173 - uninstallData.incrementErrorCount();
174 - store.write(uninstallData);
175 - }
176 } 390 }
177 - }); 391 + }
392 + }
178 393
179 - if (log.isTraceEnabled()) { 394 + private class ErrorContext extends OperationContext {
180 - log.trace("applying intent {} -> {} with {} rules: {}", 395 + @Override
181 - toUninstall.map(x -> x.key().toString()).orElse("<empty>"), 396 + void apply() {
182 - toInstall.map(x -> x.key().toString()).orElse("<empty>"), 397 + throw new UnsupportedOperationException("Unsupported installable intent");
183 - operations.stages().stream().mapToLong(Set::size).sum(),
184 - operations.stages());
185 } 398 }
186 399
187 - flowRuleService.apply(operations); 400 + @Override
401 + Object error() {
402 + return null;
403 + }
404 +
405 + @Override
406 + void prepareIntents(List<Intent> intentsToApply, Direction direction) {
407 + }
188 } 408 }
189 } 409 }
......