blob: 1e569c4f7153e0455909a0877823928dadb1fe32 [file] [log] [blame]
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.net.intent.impl;
18
Ray Milkeyfd724402016-05-26 14:45:46 -070019import com.google.common.collect.Lists;
Thomas Vachuska2980c972016-02-23 20:58:49 -080020import com.google.common.collect.Sets;
21import org.onosproject.net.DeviceId;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080022import org.onosproject.net.flow.FlowRule;
23import org.onosproject.net.flow.FlowRuleOperations;
24import org.onosproject.net.flow.FlowRuleOperationsContext;
25import org.onosproject.net.flow.FlowRuleService;
26import org.onosproject.net.flowobjective.FlowObjectiveService;
Thomas Vachuska2980c972016-02-23 20:58:49 -080027import org.onosproject.net.flowobjective.Objective;
28import org.onosproject.net.flowobjective.ObjectiveContext;
29import org.onosproject.net.flowobjective.ObjectiveError;
30import org.onosproject.net.intent.FlowObjectiveIntent;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080031import org.onosproject.net.intent.FlowRuleIntent;
32import org.onosproject.net.intent.Intent;
33import org.onosproject.net.intent.IntentData;
34import org.onosproject.net.intent.IntentStore;
35import org.slf4j.Logger;
36
Thomas Vachuska2980c972016-02-23 20:58:49 -080037import java.util.ArrayList;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080038import java.util.Collection;
Brian O'Connora78f0602016-09-22 10:56:08 -070039import java.util.Collections;
helenyrwue6aaa332016-08-05 15:41:42 -070040import java.util.Iterator;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080041import java.util.List;
42import java.util.Optional;
43import java.util.Set;
Thomas Vachuska2980c972016-02-23 20:58:49 -080044import java.util.function.Consumer;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080045import java.util.stream.Collectors;
46
Thomas Vachuska2980c972016-02-23 20:58:49 -080047import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080048import static org.onosproject.net.intent.IntentState.*;
49import static org.slf4j.LoggerFactory.getLogger;
50
51/**
52 * Auxiliary entity responsible for installing the intents into the environment.
53 */
54class IntentInstaller {
55
56 private static final Logger log = getLogger(IntentManager.class);
57
58 private IntentStore store;
59 private ObjectiveTrackerService trackerService;
60 private FlowRuleService flowRuleService;
61 private FlowObjectiveService flowObjectiveService;
62
63 private enum Direction {
64 ADD,
65 REMOVE
66 }
67
68 /**
69 * Initializes the installer with references to required services.
70 *
71 * @param intentStore intent store
72 * @param trackerService objective tracking service
73 * @param flowRuleService flow rule service
74 * @param flowObjectiveService flow objective service
75 */
76 void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
77 FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService) {
78 this.store = intentStore;
79 this.trackerService = trackerService;
80 this.flowRuleService = flowRuleService;
81 this.flowObjectiveService = flowObjectiveService;
82 }
83
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080084
85 // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
Thomas Vachuska2980c972016-02-23 20:58:49 -080086 // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
87 // This will be addressed in intent domains work; not now.
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080088
89 /**
90 * Applies the specified intent updates to the environment by uninstalling
91 * and installing the intents and updating the store references appropriately.
92 *
93 * @param toUninstall optional intent to uninstall
94 * @param toInstall optional intent to install
95 */
96 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
Thomas Vachuska2980c972016-02-23 20:58:49 -080097 // Hook for handling success
98 Consumer<OperationContext> successConsumer = (ctx) -> {
99 if (toInstall.isPresent()) {
100 IntentData installData = toInstall.get();
101 log.debug("Completed installing: {}", installData.key());
102 installData.setState(INSTALLED);
103 store.write(installData);
104 } else if (toUninstall.isPresent()) {
105 IntentData uninstallData = toUninstall.get();
106 log.debug("Completed withdrawing: {}", uninstallData.key());
107 switch (uninstallData.request()) {
108 case INSTALL_REQ:
109 uninstallData.setState(FAILED);
110 break;
111 case WITHDRAW_REQ:
112 default: //TODO "default" case should not happen
113 uninstallData.setState(WITHDRAWN);
114 break;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800115 }
Brian O'Connora78f0602016-09-22 10:56:08 -0700116 // Intent has been withdrawn; we can clear the installables
117 store.write(new IntentData(uninstallData, Collections.emptyList()));
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800118 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800119 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800120
Thomas Vachuska2980c972016-02-23 20:58:49 -0800121 // Hook for handling errors
122 Consumer<OperationContext> errorConsumer = (ctx) -> {
123 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
124 if (toInstall.isPresent()) {
125 IntentData installData = toInstall.get();
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700126 log.warn("Failed installation: {} {} due to {}",
Thomas Vachuska2980c972016-02-23 20:58:49 -0800127 installData.key(), installData.intent(), ctx.error());
128 installData.setState(CORRUPT);
129 installData.incrementErrorCount();
130 store.write(installData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800131 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800132 // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
133 if (toUninstall.isPresent()) {
134 IntentData uninstallData = toUninstall.get();
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700135 log.warn("Failed withdrawal: {} {} due to {}",
Thomas Vachuska2980c972016-02-23 20:58:49 -0800136 uninstallData.key(), uninstallData.intent(), ctx.error());
137 uninstallData.setState(CORRUPT);
138 uninstallData.incrementErrorCount();
139 store.write(uninstallData);
140 }
141 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800142
Thomas Vachuska2980c972016-02-23 20:58:49 -0800143 // Create a context for tracking the backing operations for applying
144 // the intents to the environment.
145 OperationContext context = createContext(toUninstall, toInstall);
146
147 context.prepare(toUninstall, toInstall, successConsumer, errorConsumer);
148 context.apply();
149 }
150
151 // ------ Utilities to support FlowRule vs. FlowObjective behavior -------
152
153 // Creates the context appropriate for tracking operations of the
154 // the specified intents.
155 private OperationContext createContext(Optional<IntentData> toUninstall,
156 Optional<IntentData> toInstall) {
157 if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
158 return new FlowRuleOperationContext();
159 }
160 if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
161 return new FlowObjectiveOperationContext();
162 }
163 return new ErrorContext();
164 }
165
166 private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
167 Class<? extends Intent> intentClass) {
168 boolean notBothNull = false;
169 if (toInstall.isPresent()) {
170 notBothNull = true;
171 if (!toInstall.get().installables().stream()
172 .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
173 return false;
174 }
175 }
176 if (toUninstall.isPresent()) {
177 notBothNull = true;
178 if (!toUninstall.get().installables().stream()
179 .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
180 return false;
181 }
182 }
183 return notBothNull;
184 }
185
186 // Base context for applying and tracking operations related to installable intents.
187 private abstract class OperationContext {
188 protected Optional<IntentData> toUninstall;
189 protected Optional<IntentData> toInstall;
190 protected Consumer<OperationContext> successConsumer;
191 protected Consumer<OperationContext> errorConsumer;
192
193 abstract void apply();
194
195 abstract Object error();
196
197 abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
198
199 void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
200 Consumer<OperationContext> successConsumer,
201 Consumer<OperationContext> errorConsumer) {
202 this.toUninstall = toUninstall;
203 this.toInstall = toInstall;
204 this.successConsumer = successConsumer;
205 this.errorConsumer = errorConsumer;
helenyrwue6aaa332016-08-05 15:41:42 -0700206 prepareIntentData(toUninstall, toInstall);
207 }
208
209 private void prepareIntentData(Optional<IntentData> uninstallData,
210 Optional<IntentData> installData) {
211 if (!installData.isPresent() && !uninstallData.isPresent()) {
212 return;
213 } else if (!installData.isPresent()) {
214 prepareIntentData(uninstallData, Direction.REMOVE);
215 } else if (!uninstallData.isPresent()) {
216 prepareIntentData(installData, Direction.ADD);
217 } else {
218 IntentData uninstall = uninstallData.get();
219 IntentData install = installData.get();
Brian O'Connor09d90f02016-09-13 11:06:14 -0700220 List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables());
221 List<Intent> installIntents = Lists.newArrayList(install.installables());
helenyrwue6aaa332016-08-05 15:41:42 -0700222
223 checkState(uninstallIntents.stream().allMatch(this::isSupported),
224 "Unsupported installable intents detected");
225 checkState(installIntents.stream().allMatch(this::isSupported),
226 "Unsupported installable intents detected");
227
228 //TODO: Filter FlowObjective intents
229 // Filter out same intents and intents with same flow rules
230 Iterator<Intent> iterator = installIntents.iterator();
231 while (iterator.hasNext()) {
232 Intent installIntent = iterator.next();
233 uninstallIntents.stream().filter(uIntent -> {
234 if (uIntent.equals(installIntent)) {
235 return true;
236 } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) {
Brian O'Connor09d90f02016-09-13 11:06:14 -0700237 //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis
238 // (direction can be implied from intent state)
helenyrwue6aaa332016-08-05 15:41:42 -0700239 return ((FlowRuleIntent) uIntent).flowRules()
240 .containsAll(((FlowRuleIntent) installIntent).flowRules());
241 } else {
242 return false;
243 }
244 }).findFirst().ifPresent(common -> {
245 uninstallIntents.remove(common);
Brian O'Connor09d90f02016-09-13 11:06:14 -0700246 if (INSTALLED.equals(uninstall.state())) {
247 // only remove the install intent if the existing
248 // intent (i.e. the uninstall one) is already
249 // installed or installing
250 iterator.remove();
251 }
helenyrwue6aaa332016-08-05 15:41:42 -0700252 });
253 }
254
255 final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
256 final IntentData newInstall = new IntentData(install, installIntents);
257
258 trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
259 uninstallIntents.forEach(installable ->
260 trackerService.removeTrackedResources(newUninstall.intent().key(),
261 installable.resources()));
262 trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
263 installIntents.forEach(installable ->
264 trackerService.addTrackedResources(newInstall.key(),
265 installable.resources()));
266 prepareIntents(uninstallIntents, Direction.REMOVE);
267 prepareIntents(installIntents, Direction.ADD);
268 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800269 }
270
Thomas Vachuska2980c972016-02-23 20:58:49 -0800271 /**
272 * Applies the specified intent data, if present, to the network using the
273 * specified context.
274 *
275 * @param intentData optional intent data; no-op if not present
276 * @param direction indicates adding or removal
277 */
278 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
279 if (!intentData.isPresent()) {
280 return;
281 }
282
283 IntentData data = intentData.get();
284 List<Intent> intentsToApply = data.installables();
285 checkState(intentsToApply.stream().allMatch(this::isSupported),
286 "Unsupported installable intents detected");
287
288 if (direction == Direction.ADD) {
289 trackerService.addTrackedResources(data.key(), data.intent().resources());
290 intentsToApply.forEach(installable ->
291 trackerService.addTrackedResources(data.key(),
292 installable.resources()));
293 } else {
294 trackerService.removeTrackedResources(data.key(), data.intent().resources());
295 intentsToApply.forEach(installable ->
296 trackerService.removeTrackedResources(data.intent().key(),
297 installable.resources()));
298 }
299
300 prepareIntents(intentsToApply, direction);
301 }
302
303 private boolean isSupported(Intent intent) {
304 return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
305 }
306 }
307
308
309 // Context for applying and tracking operations related to flow rule intent.
310 private class FlowRuleOperationContext extends OperationContext {
311 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
312 FlowRuleOperationsContext flowRuleOperationsContext;
313
314 void apply() {
315 flowRuleOperationsContext = new FlowRuleOperationsContext() {
316 @Override
317 public void onSuccess(FlowRuleOperations ops) {
318 successConsumer.accept(FlowRuleOperationContext.this);
319 }
320
321 @Override
322 public void onError(FlowRuleOperations ops) {
323 errorConsumer.accept(FlowRuleOperationContext.this);
324 }
325 };
326 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
327
328 if (log.isTraceEnabled()) {
329 log.trace("applying intent {} -> {} with {} rules: {}",
330 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
331 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
332 operations.stages().stream().mapToLong(Set::size).sum(),
333 operations.stages());
334 }
335
336 flowRuleService.apply(operations);
337 }
338
339 @Override
340 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
341 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
342 builder.newStage();
343
344 List<Collection<FlowRule>> stages = intentsToApply.stream()
345 .map(x -> (FlowRuleIntent) x)
346 .map(FlowRuleIntent::flowRules)
347 .collect(Collectors.toList());
348
349 for (Collection<FlowRule> rules : stages) {
350 if (direction == Direction.ADD) {
351 rules.forEach(builder::add);
352 } else {
353 rules.forEach(builder::remove);
354 }
355 }
356
357 }
358
359 @Override
360 public Object error() {
361 return flowRuleOperationsContext;
362 }
363 }
364
365 // Context for applying and tracking operations related to flow objective intents.
366 private class FlowObjectiveOperationContext extends OperationContext {
Ray Milkeyfd724402016-05-26 14:45:46 -0700367 List<FlowObjectiveInstallationContext> contexts = Lists.newLinkedList();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800368 final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
369 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
370
371 @Override
372 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Ray Milkeyfd724402016-05-26 14:45:46 -0700373 intentsToApply.stream()
Thomas Vachuska2980c972016-02-23 20:58:49 -0800374 .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
Ray Milkeyfd724402016-05-26 14:45:46 -0700375 .forEach(contexts::add);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800376 }
377
378 // Builds the specified objective in the appropriate direction
379 private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
380 Direction direction) {
381 int size = intent.objectives().size();
382 List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
383 for (int i = 0; i < size; i++) {
384 DeviceId deviceId = intent.devices().get(i);
385 Objective.Builder builder = intent.objectives().get(i).copy();
386 FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
387
388 final Objective objective;
389 switch (direction) {
390 case ADD:
391 objective = builder.add(context);
392 break;
393 case REMOVE:
394 objective = builder.remove(context);
395 break;
396 default:
397 throw new UnsupportedOperationException("Unsupported direction " + direction);
398 }
399 context.setObjective(objective, deviceId);
400 contexts.add(context);
401 }
402 return contexts;
403 }
404
405 @Override
406 void apply() {
407 contexts.forEach(objectiveContext -> {
408 pendingContexts.add(objectiveContext);
409 flowObjectiveService.apply(objectiveContext.deviceId,
410 objectiveContext.objective);
411 });
412 }
413
414 @Override
415 public Object error() {
416 return errorContexts;
417 }
418
419 private class FlowObjectiveInstallationContext implements ObjectiveContext {
420 Objective objective;
421 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700422 ObjectiveError error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800423
424 void setObjective(Objective objective, DeviceId deviceId) {
425 this.objective = objective;
426 this.deviceId = deviceId;
427 }
428
429 @Override
430 public void onSuccess(Objective objective) {
431 finish();
432 }
433
434 @Override
435 public void onError(Objective objective, ObjectiveError error) {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700436 this.error = error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800437 errorContexts.add(this);
438 finish();
439 }
440
441 private void finish() {
442 synchronized (pendingContexts) {
443 pendingContexts.remove(this);
444 if (pendingContexts.isEmpty()) {
445 if (errorContexts.isEmpty()) {
446 successConsumer.accept(FlowObjectiveOperationContext.this);
447 } else {
448 errorConsumer.accept(FlowObjectiveOperationContext.this);
449 }
450 }
451 }
452 }
453
454 @Override
455 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700456 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800457 }
458 }
459 }
460
461 private class ErrorContext extends OperationContext {
462 @Override
463 void apply() {
464 throw new UnsupportedOperationException("Unsupported installable intent");
465 }
466
467 @Override
468 Object error() {
469 return null;
470 }
471
472 @Override
473 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
474 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800475 }
476}