blob: 49433af0a064d06e9b74ac9943de80e2a0350ab5 [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
Brian O'Connorc590ebb2016-12-08 18:16:41 -080056 private static final Logger log = getLogger(IntentInstaller.class);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080057
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;
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700190 /**
191 * Implementation of {@link OperationContext} should call this on success.
192 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800193 protected Consumer<OperationContext> successConsumer;
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700194 /**
195 * Implementation of {@link OperationContext} should call this on error.
196 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800197 protected Consumer<OperationContext> errorConsumer;
198
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700199 /**
200 * Applies the Intents specified by
201 * {@link #prepareIntents(List, Direction)} call(s) prior to this call.
202 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800203 abstract void apply();
204
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700205 /**
206 * Returns error state of the context.
207 * <p>
208 * Used for error logging purpose.
209 * Returned Object should have reasonable toString() implementation.
210 * @return context state, describing current error state
211 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800212 abstract Object error();
213
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700214 /**
215 * Prepares Intent(s) to {@link #apply() apply} in this operation.
216 * <p>
217 * Intents specified by {@code intentsToApply} in a single call
218 * can be applied to the Devices in arbitrary order.
219 * But group of Intents specified in consecutive {@link #prepareIntents(List, Direction)}
220 * calls must be applied in order. (e.g., guarded by barrier)
221 *
222 * @param intentsToApply {@link Intent}s to apply
223 * @param direction of operation
224 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800225 abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
226
227 void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
228 Consumer<OperationContext> successConsumer,
229 Consumer<OperationContext> errorConsumer) {
230 this.toUninstall = toUninstall;
231 this.toInstall = toInstall;
232 this.successConsumer = successConsumer;
233 this.errorConsumer = errorConsumer;
helenyrwue6aaa332016-08-05 15:41:42 -0700234 prepareIntentData(toUninstall, toInstall);
235 }
236
237 private void prepareIntentData(Optional<IntentData> uninstallData,
238 Optional<IntentData> installData) {
239 if (!installData.isPresent() && !uninstallData.isPresent()) {
240 return;
241 } else if (!installData.isPresent()) {
242 prepareIntentData(uninstallData, Direction.REMOVE);
243 } else if (!uninstallData.isPresent()) {
244 prepareIntentData(installData, Direction.ADD);
245 } else {
246 IntentData uninstall = uninstallData.get();
247 IntentData install = installData.get();
Brian O'Connor09d90f02016-09-13 11:06:14 -0700248 List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables());
249 List<Intent> installIntents = Lists.newArrayList(install.installables());
helenyrwue6aaa332016-08-05 15:41:42 -0700250
251 checkState(uninstallIntents.stream().allMatch(this::isSupported),
252 "Unsupported installable intents detected");
253 checkState(installIntents.stream().allMatch(this::isSupported),
254 "Unsupported installable intents detected");
255
256 //TODO: Filter FlowObjective intents
257 // Filter out same intents and intents with same flow rules
258 Iterator<Intent> iterator = installIntents.iterator();
259 while (iterator.hasNext()) {
260 Intent installIntent = iterator.next();
261 uninstallIntents.stream().filter(uIntent -> {
262 if (uIntent.equals(installIntent)) {
263 return true;
264 } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) {
Brian O'Connor09d90f02016-09-13 11:06:14 -0700265 //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis
266 // (direction can be implied from intent state)
helenyrwue6aaa332016-08-05 15:41:42 -0700267 return ((FlowRuleIntent) uIntent).flowRules()
268 .containsAll(((FlowRuleIntent) installIntent).flowRules());
269 } else {
270 return false;
271 }
272 }).findFirst().ifPresent(common -> {
273 uninstallIntents.remove(common);
Brian O'Connor09d90f02016-09-13 11:06:14 -0700274 if (INSTALLED.equals(uninstall.state())) {
275 // only remove the install intent if the existing
276 // intent (i.e. the uninstall one) is already
277 // installed or installing
278 iterator.remove();
279 }
helenyrwue6aaa332016-08-05 15:41:42 -0700280 });
281 }
282
283 final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
284 final IntentData newInstall = new IntentData(install, installIntents);
285
286 trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
287 uninstallIntents.forEach(installable ->
288 trackerService.removeTrackedResources(newUninstall.intent().key(),
289 installable.resources()));
290 trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
291 installIntents.forEach(installable ->
292 trackerService.addTrackedResources(newInstall.key(),
293 installable.resources()));
294 prepareIntents(uninstallIntents, Direction.REMOVE);
295 prepareIntents(installIntents, Direction.ADD);
296 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800297 }
298
Thomas Vachuska2980c972016-02-23 20:58:49 -0800299 /**
300 * Applies the specified intent data, if present, to the network using the
301 * specified context.
302 *
303 * @param intentData optional intent data; no-op if not present
304 * @param direction indicates adding or removal
305 */
306 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
307 if (!intentData.isPresent()) {
308 return;
309 }
310
311 IntentData data = intentData.get();
312 List<Intent> intentsToApply = data.installables();
313 checkState(intentsToApply.stream().allMatch(this::isSupported),
314 "Unsupported installable intents detected");
315
316 if (direction == Direction.ADD) {
317 trackerService.addTrackedResources(data.key(), data.intent().resources());
318 intentsToApply.forEach(installable ->
319 trackerService.addTrackedResources(data.key(),
320 installable.resources()));
321 } else {
322 trackerService.removeTrackedResources(data.key(), data.intent().resources());
323 intentsToApply.forEach(installable ->
324 trackerService.removeTrackedResources(data.intent().key(),
325 installable.resources()));
326 }
327
328 prepareIntents(intentsToApply, direction);
329 }
330
331 private boolean isSupported(Intent intent) {
332 return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
333 }
334 }
335
336
337 // Context for applying and tracking operations related to flow rule intent.
338 private class FlowRuleOperationContext extends OperationContext {
339 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
340 FlowRuleOperationsContext flowRuleOperationsContext;
341
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700342 @Override
Thomas Vachuska2980c972016-02-23 20:58:49 -0800343 void apply() {
344 flowRuleOperationsContext = new FlowRuleOperationsContext() {
345 @Override
346 public void onSuccess(FlowRuleOperations ops) {
347 successConsumer.accept(FlowRuleOperationContext.this);
348 }
349
350 @Override
351 public void onError(FlowRuleOperations ops) {
352 errorConsumer.accept(FlowRuleOperationContext.this);
353 }
354 };
355 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
356
357 if (log.isTraceEnabled()) {
358 log.trace("applying intent {} -> {} with {} rules: {}",
359 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
360 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
361 operations.stages().stream().mapToLong(Set::size).sum(),
362 operations.stages());
363 }
364
365 flowRuleService.apply(operations);
366 }
367
368 @Override
369 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
370 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
371 builder.newStage();
372
373 List<Collection<FlowRule>> stages = intentsToApply.stream()
374 .map(x -> (FlowRuleIntent) x)
375 .map(FlowRuleIntent::flowRules)
376 .collect(Collectors.toList());
377
378 for (Collection<FlowRule> rules : stages) {
379 if (direction == Direction.ADD) {
380 rules.forEach(builder::add);
381 } else {
382 rules.forEach(builder::remove);
383 }
384 }
385
386 }
387
388 @Override
389 public Object error() {
390 return flowRuleOperationsContext;
391 }
392 }
393
394 // Context for applying and tracking operations related to flow objective intents.
395 private class FlowObjectiveOperationContext extends OperationContext {
Ray Milkeyfd724402016-05-26 14:45:46 -0700396 List<FlowObjectiveInstallationContext> contexts = Lists.newLinkedList();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800397 final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
398 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
399
400 @Override
401 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Ray Milkeyfd724402016-05-26 14:45:46 -0700402 intentsToApply.stream()
Thomas Vachuska2980c972016-02-23 20:58:49 -0800403 .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
Ray Milkeyfd724402016-05-26 14:45:46 -0700404 .forEach(contexts::add);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800405 }
406
407 // Builds the specified objective in the appropriate direction
408 private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
409 Direction direction) {
410 int size = intent.objectives().size();
411 List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
412 for (int i = 0; i < size; i++) {
413 DeviceId deviceId = intent.devices().get(i);
414 Objective.Builder builder = intent.objectives().get(i).copy();
415 FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
416
417 final Objective objective;
418 switch (direction) {
419 case ADD:
420 objective = builder.add(context);
421 break;
422 case REMOVE:
423 objective = builder.remove(context);
424 break;
425 default:
426 throw new UnsupportedOperationException("Unsupported direction " + direction);
427 }
428 context.setObjective(objective, deviceId);
429 contexts.add(context);
430 }
431 return contexts;
432 }
433
434 @Override
435 void apply() {
Pier Ventre2c433ce2016-12-13 13:23:02 -0800436 pendingContexts.addAll(contexts);
437 contexts.forEach(objectiveContext ->
Thomas Vachuska2980c972016-02-23 20:58:49 -0800438 flowObjectiveService.apply(objectiveContext.deviceId,
Pier Ventre2c433ce2016-12-13 13:23:02 -0800439 objectiveContext.objective)
440 );
Thomas Vachuska2980c972016-02-23 20:58:49 -0800441 }
442
443 @Override
444 public Object error() {
445 return errorContexts;
446 }
447
448 private class FlowObjectiveInstallationContext implements ObjectiveContext {
449 Objective objective;
450 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700451 ObjectiveError error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800452
453 void setObjective(Objective objective, DeviceId deviceId) {
454 this.objective = objective;
455 this.deviceId = deviceId;
456 }
457
458 @Override
459 public void onSuccess(Objective objective) {
460 finish();
461 }
462
463 @Override
464 public void onError(Objective objective, ObjectiveError error) {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700465 this.error = error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800466 errorContexts.add(this);
467 finish();
468 }
469
470 private void finish() {
471 synchronized (pendingContexts) {
472 pendingContexts.remove(this);
473 if (pendingContexts.isEmpty()) {
474 if (errorContexts.isEmpty()) {
475 successConsumer.accept(FlowObjectiveOperationContext.this);
476 } else {
477 errorConsumer.accept(FlowObjectiveOperationContext.this);
478 }
479 }
480 }
481 }
482
483 @Override
484 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700485 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800486 }
487 }
488 }
489
490 private class ErrorContext extends OperationContext {
491 @Override
492 void apply() {
493 throw new UnsupportedOperationException("Unsupported installable intent");
494 }
495
496 @Override
497 Object error() {
498 return null;
499 }
500
501 @Override
502 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
503 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800504 }
505}