blob: 332ca733833afeb7e1f9fecbe7bf063382ba8aab [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;
helenyrwue6aaa332016-08-05 15:41:42 -070039import java.util.Iterator;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080040import java.util.List;
41import java.util.Optional;
42import java.util.Set;
Thomas Vachuska2980c972016-02-23 20:58:49 -080043import java.util.function.Consumer;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080044import java.util.stream.Collectors;
45
Thomas Vachuska2980c972016-02-23 20:58:49 -080046import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080047import static org.onosproject.net.intent.IntentState.*;
48import static org.slf4j.LoggerFactory.getLogger;
49
50/**
51 * Auxiliary entity responsible for installing the intents into the environment.
52 */
53class IntentInstaller {
54
55 private static final Logger log = getLogger(IntentManager.class);
56
57 private IntentStore store;
58 private ObjectiveTrackerService trackerService;
59 private FlowRuleService flowRuleService;
60 private FlowObjectiveService flowObjectiveService;
61
62 private enum Direction {
63 ADD,
64 REMOVE
65 }
66
67 /**
68 * Initializes the installer with references to required services.
69 *
70 * @param intentStore intent store
71 * @param trackerService objective tracking service
72 * @param flowRuleService flow rule service
73 * @param flowObjectiveService flow objective service
74 */
75 void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
76 FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService) {
77 this.store = intentStore;
78 this.trackerService = trackerService;
79 this.flowRuleService = flowRuleService;
80 this.flowObjectiveService = flowObjectiveService;
81 }
82
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080083
84 // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
Thomas Vachuska2980c972016-02-23 20:58:49 -080085 // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
86 // This will be addressed in intent domains work; not now.
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080087
88 /**
89 * Applies the specified intent updates to the environment by uninstalling
90 * and installing the intents and updating the store references appropriately.
91 *
92 * @param toUninstall optional intent to uninstall
93 * @param toInstall optional intent to install
94 */
95 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
Thomas Vachuska2980c972016-02-23 20:58:49 -080096 // Hook for handling success
97 Consumer<OperationContext> successConsumer = (ctx) -> {
98 if (toInstall.isPresent()) {
99 IntentData installData = toInstall.get();
100 log.debug("Completed installing: {}", installData.key());
101 installData.setState(INSTALLED);
102 store.write(installData);
103 } else if (toUninstall.isPresent()) {
104 IntentData uninstallData = toUninstall.get();
105 log.debug("Completed withdrawing: {}", uninstallData.key());
106 switch (uninstallData.request()) {
107 case INSTALL_REQ:
108 uninstallData.setState(FAILED);
109 break;
110 case WITHDRAW_REQ:
111 default: //TODO "default" case should not happen
112 uninstallData.setState(WITHDRAWN);
113 break;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800114 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800115 store.write(uninstallData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800116 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800117 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800118
Thomas Vachuska2980c972016-02-23 20:58:49 -0800119 // Hook for handling errors
120 Consumer<OperationContext> errorConsumer = (ctx) -> {
121 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
122 if (toInstall.isPresent()) {
123 IntentData installData = toInstall.get();
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700124 log.warn("Failed installation: {} {} due to {}",
Thomas Vachuska2980c972016-02-23 20:58:49 -0800125 installData.key(), installData.intent(), ctx.error());
126 installData.setState(CORRUPT);
127 installData.incrementErrorCount();
128 store.write(installData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800129 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800130 // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
131 if (toUninstall.isPresent()) {
132 IntentData uninstallData = toUninstall.get();
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700133 log.warn("Failed withdrawal: {} {} due to {}",
Thomas Vachuska2980c972016-02-23 20:58:49 -0800134 uninstallData.key(), uninstallData.intent(), ctx.error());
135 uninstallData.setState(CORRUPT);
136 uninstallData.incrementErrorCount();
137 store.write(uninstallData);
138 }
139 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800140
Thomas Vachuska2980c972016-02-23 20:58:49 -0800141 // Create a context for tracking the backing operations for applying
142 // the intents to the environment.
143 OperationContext context = createContext(toUninstall, toInstall);
144
145 context.prepare(toUninstall, toInstall, successConsumer, errorConsumer);
146 context.apply();
147 }
148
149 // ------ Utilities to support FlowRule vs. FlowObjective behavior -------
150
151 // Creates the context appropriate for tracking operations of the
152 // the specified intents.
153 private OperationContext createContext(Optional<IntentData> toUninstall,
154 Optional<IntentData> toInstall) {
155 if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
156 return new FlowRuleOperationContext();
157 }
158 if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
159 return new FlowObjectiveOperationContext();
160 }
161 return new ErrorContext();
162 }
163
164 private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
165 Class<? extends Intent> intentClass) {
166 boolean notBothNull = false;
167 if (toInstall.isPresent()) {
168 notBothNull = true;
169 if (!toInstall.get().installables().stream()
170 .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
171 return false;
172 }
173 }
174 if (toUninstall.isPresent()) {
175 notBothNull = true;
176 if (!toUninstall.get().installables().stream()
177 .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
178 return false;
179 }
180 }
181 return notBothNull;
182 }
183
184 // Base context for applying and tracking operations related to installable intents.
185 private abstract class OperationContext {
186 protected Optional<IntentData> toUninstall;
187 protected Optional<IntentData> toInstall;
188 protected Consumer<OperationContext> successConsumer;
189 protected Consumer<OperationContext> errorConsumer;
190
191 abstract void apply();
192
193 abstract Object error();
194
195 abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
196
197 void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
198 Consumer<OperationContext> successConsumer,
199 Consumer<OperationContext> errorConsumer) {
200 this.toUninstall = toUninstall;
201 this.toInstall = toInstall;
202 this.successConsumer = successConsumer;
203 this.errorConsumer = errorConsumer;
helenyrwue6aaa332016-08-05 15:41:42 -0700204 prepareIntentData(toUninstall, toInstall);
205 }
206
207 private void prepareIntentData(Optional<IntentData> uninstallData,
208 Optional<IntentData> installData) {
209 if (!installData.isPresent() && !uninstallData.isPresent()) {
210 return;
211 } else if (!installData.isPresent()) {
212 prepareIntentData(uninstallData, Direction.REMOVE);
213 } else if (!uninstallData.isPresent()) {
214 prepareIntentData(installData, Direction.ADD);
215 } else {
216 IntentData uninstall = uninstallData.get();
217 IntentData install = installData.get();
Brian O'Connor09d90f02016-09-13 11:06:14 -0700218 List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables());
219 List<Intent> installIntents = Lists.newArrayList(install.installables());
helenyrwue6aaa332016-08-05 15:41:42 -0700220
221 checkState(uninstallIntents.stream().allMatch(this::isSupported),
222 "Unsupported installable intents detected");
223 checkState(installIntents.stream().allMatch(this::isSupported),
224 "Unsupported installable intents detected");
225
226 //TODO: Filter FlowObjective intents
227 // Filter out same intents and intents with same flow rules
228 Iterator<Intent> iterator = installIntents.iterator();
229 while (iterator.hasNext()) {
230 Intent installIntent = iterator.next();
231 uninstallIntents.stream().filter(uIntent -> {
232 if (uIntent.equals(installIntent)) {
233 return true;
234 } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) {
Brian O'Connor09d90f02016-09-13 11:06:14 -0700235 //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis
236 // (direction can be implied from intent state)
helenyrwue6aaa332016-08-05 15:41:42 -0700237 return ((FlowRuleIntent) uIntent).flowRules()
238 .containsAll(((FlowRuleIntent) installIntent).flowRules());
239 } else {
240 return false;
241 }
242 }).findFirst().ifPresent(common -> {
243 uninstallIntents.remove(common);
Brian O'Connor09d90f02016-09-13 11:06:14 -0700244 if (INSTALLED.equals(uninstall.state())) {
245 // only remove the install intent if the existing
246 // intent (i.e. the uninstall one) is already
247 // installed or installing
248 iterator.remove();
249 }
helenyrwue6aaa332016-08-05 15:41:42 -0700250 });
251 }
252
253 final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
254 final IntentData newInstall = new IntentData(install, installIntents);
255
256 trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
257 uninstallIntents.forEach(installable ->
258 trackerService.removeTrackedResources(newUninstall.intent().key(),
259 installable.resources()));
260 trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
261 installIntents.forEach(installable ->
262 trackerService.addTrackedResources(newInstall.key(),
263 installable.resources()));
264 prepareIntents(uninstallIntents, Direction.REMOVE);
265 prepareIntents(installIntents, Direction.ADD);
266 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800267 }
268
Thomas Vachuska2980c972016-02-23 20:58:49 -0800269 /**
270 * Applies the specified intent data, if present, to the network using the
271 * specified context.
272 *
273 * @param intentData optional intent data; no-op if not present
274 * @param direction indicates adding or removal
275 */
276 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
277 if (!intentData.isPresent()) {
278 return;
279 }
280
281 IntentData data = intentData.get();
282 List<Intent> intentsToApply = data.installables();
283 checkState(intentsToApply.stream().allMatch(this::isSupported),
284 "Unsupported installable intents detected");
285
286 if (direction == Direction.ADD) {
287 trackerService.addTrackedResources(data.key(), data.intent().resources());
288 intentsToApply.forEach(installable ->
289 trackerService.addTrackedResources(data.key(),
290 installable.resources()));
291 } else {
292 trackerService.removeTrackedResources(data.key(), data.intent().resources());
293 intentsToApply.forEach(installable ->
294 trackerService.removeTrackedResources(data.intent().key(),
295 installable.resources()));
296 }
297
298 prepareIntents(intentsToApply, direction);
299 }
300
301 private boolean isSupported(Intent intent) {
302 return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
303 }
304 }
305
306
307 // Context for applying and tracking operations related to flow rule intent.
308 private class FlowRuleOperationContext extends OperationContext {
309 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
310 FlowRuleOperationsContext flowRuleOperationsContext;
311
312 void apply() {
313 flowRuleOperationsContext = new FlowRuleOperationsContext() {
314 @Override
315 public void onSuccess(FlowRuleOperations ops) {
316 successConsumer.accept(FlowRuleOperationContext.this);
317 }
318
319 @Override
320 public void onError(FlowRuleOperations ops) {
321 errorConsumer.accept(FlowRuleOperationContext.this);
322 }
323 };
324 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
325
326 if (log.isTraceEnabled()) {
327 log.trace("applying intent {} -> {} with {} rules: {}",
328 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
329 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
330 operations.stages().stream().mapToLong(Set::size).sum(),
331 operations.stages());
332 }
333
334 flowRuleService.apply(operations);
335 }
336
337 @Override
338 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
339 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
340 builder.newStage();
341
342 List<Collection<FlowRule>> stages = intentsToApply.stream()
343 .map(x -> (FlowRuleIntent) x)
344 .map(FlowRuleIntent::flowRules)
345 .collect(Collectors.toList());
346
347 for (Collection<FlowRule> rules : stages) {
348 if (direction == Direction.ADD) {
349 rules.forEach(builder::add);
350 } else {
351 rules.forEach(builder::remove);
352 }
353 }
354
355 }
356
357 @Override
358 public Object error() {
359 return flowRuleOperationsContext;
360 }
361 }
362
363 // Context for applying and tracking operations related to flow objective intents.
364 private class FlowObjectiveOperationContext extends OperationContext {
Ray Milkeyfd724402016-05-26 14:45:46 -0700365 List<FlowObjectiveInstallationContext> contexts = Lists.newLinkedList();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800366 final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
367 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
368
369 @Override
370 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Ray Milkeyfd724402016-05-26 14:45:46 -0700371 intentsToApply.stream()
Thomas Vachuska2980c972016-02-23 20:58:49 -0800372 .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
Ray Milkeyfd724402016-05-26 14:45:46 -0700373 .forEach(contexts::add);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800374 }
375
376 // Builds the specified objective in the appropriate direction
377 private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
378 Direction direction) {
379 int size = intent.objectives().size();
380 List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
381 for (int i = 0; i < size; i++) {
382 DeviceId deviceId = intent.devices().get(i);
383 Objective.Builder builder = intent.objectives().get(i).copy();
384 FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
385
386 final Objective objective;
387 switch (direction) {
388 case ADD:
389 objective = builder.add(context);
390 break;
391 case REMOVE:
392 objective = builder.remove(context);
393 break;
394 default:
395 throw new UnsupportedOperationException("Unsupported direction " + direction);
396 }
397 context.setObjective(objective, deviceId);
398 contexts.add(context);
399 }
400 return contexts;
401 }
402
403 @Override
404 void apply() {
405 contexts.forEach(objectiveContext -> {
406 pendingContexts.add(objectiveContext);
407 flowObjectiveService.apply(objectiveContext.deviceId,
408 objectiveContext.objective);
409 });
410 }
411
412 @Override
413 public Object error() {
414 return errorContexts;
415 }
416
417 private class FlowObjectiveInstallationContext implements ObjectiveContext {
418 Objective objective;
419 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700420 ObjectiveError error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800421
422 void setObjective(Objective objective, DeviceId deviceId) {
423 this.objective = objective;
424 this.deviceId = deviceId;
425 }
426
427 @Override
428 public void onSuccess(Objective objective) {
429 finish();
430 }
431
432 @Override
433 public void onError(Objective objective, ObjectiveError error) {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700434 this.error = error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800435 errorContexts.add(this);
436 finish();
437 }
438
439 private void finish() {
440 synchronized (pendingContexts) {
441 pendingContexts.remove(this);
442 if (pendingContexts.isEmpty()) {
443 if (errorContexts.isEmpty()) {
444 successConsumer.accept(FlowObjectiveOperationContext.this);
445 } else {
446 errorConsumer.accept(FlowObjectiveOperationContext.this);
447 }
448 }
449 }
450 }
451
452 @Override
453 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700454 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800455 }
456 }
457 }
458
459 private class ErrorContext extends OperationContext {
460 @Override
461 void apply() {
462 throw new UnsupportedOperationException("Unsupported installable intent");
463 }
464
465 @Override
466 Object error() {
467 return null;
468 }
469
470 @Override
471 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
472 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800473 }
474}