blob: 17b4653e955bda34b9e5313bd34bd46404380123 [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();
218 List<Intent> uninstallIntents = new ArrayList<>();
219 uninstallIntents.addAll(uninstall.installables());
220 List<Intent> installIntents = new ArrayList<>();
221 installIntents.addAll(install.installables());
222
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) {
237 return ((FlowRuleIntent) uIntent).flowRules()
238 .containsAll(((FlowRuleIntent) installIntent).flowRules());
239 } else {
240 return false;
241 }
242 }).findFirst().ifPresent(common -> {
243 uninstallIntents.remove(common);
244 iterator.remove();
245 });
246 }
247
248 final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
249 final IntentData newInstall = new IntentData(install, installIntents);
250
251 trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
252 uninstallIntents.forEach(installable ->
253 trackerService.removeTrackedResources(newUninstall.intent().key(),
254 installable.resources()));
255 trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
256 installIntents.forEach(installable ->
257 trackerService.addTrackedResources(newInstall.key(),
258 installable.resources()));
259 prepareIntents(uninstallIntents, Direction.REMOVE);
260 prepareIntents(installIntents, Direction.ADD);
261 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800262 }
263
Thomas Vachuska2980c972016-02-23 20:58:49 -0800264 /**
265 * Applies the specified intent data, if present, to the network using the
266 * specified context.
267 *
268 * @param intentData optional intent data; no-op if not present
269 * @param direction indicates adding or removal
270 */
271 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
272 if (!intentData.isPresent()) {
273 return;
274 }
275
276 IntentData data = intentData.get();
277 List<Intent> intentsToApply = data.installables();
278 checkState(intentsToApply.stream().allMatch(this::isSupported),
279 "Unsupported installable intents detected");
280
281 if (direction == Direction.ADD) {
282 trackerService.addTrackedResources(data.key(), data.intent().resources());
283 intentsToApply.forEach(installable ->
284 trackerService.addTrackedResources(data.key(),
285 installable.resources()));
286 } else {
287 trackerService.removeTrackedResources(data.key(), data.intent().resources());
288 intentsToApply.forEach(installable ->
289 trackerService.removeTrackedResources(data.intent().key(),
290 installable.resources()));
291 }
292
293 prepareIntents(intentsToApply, direction);
294 }
295
296 private boolean isSupported(Intent intent) {
297 return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
298 }
299 }
300
301
302 // Context for applying and tracking operations related to flow rule intent.
303 private class FlowRuleOperationContext extends OperationContext {
304 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
305 FlowRuleOperationsContext flowRuleOperationsContext;
306
307 void apply() {
308 flowRuleOperationsContext = new FlowRuleOperationsContext() {
309 @Override
310 public void onSuccess(FlowRuleOperations ops) {
311 successConsumer.accept(FlowRuleOperationContext.this);
312 }
313
314 @Override
315 public void onError(FlowRuleOperations ops) {
316 errorConsumer.accept(FlowRuleOperationContext.this);
317 }
318 };
319 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
320
321 if (log.isTraceEnabled()) {
322 log.trace("applying intent {} -> {} with {} rules: {}",
323 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
324 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
325 operations.stages().stream().mapToLong(Set::size).sum(),
326 operations.stages());
327 }
328
329 flowRuleService.apply(operations);
330 }
331
332 @Override
333 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
334 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
335 builder.newStage();
336
337 List<Collection<FlowRule>> stages = intentsToApply.stream()
338 .map(x -> (FlowRuleIntent) x)
339 .map(FlowRuleIntent::flowRules)
340 .collect(Collectors.toList());
341
342 for (Collection<FlowRule> rules : stages) {
343 if (direction == Direction.ADD) {
344 rules.forEach(builder::add);
345 } else {
346 rules.forEach(builder::remove);
347 }
348 }
349
350 }
351
352 @Override
353 public Object error() {
354 return flowRuleOperationsContext;
355 }
356 }
357
358 // Context for applying and tracking operations related to flow objective intents.
359 private class FlowObjectiveOperationContext extends OperationContext {
Ray Milkeyfd724402016-05-26 14:45:46 -0700360 List<FlowObjectiveInstallationContext> contexts = Lists.newLinkedList();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800361 final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
362 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
363
364 @Override
365 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Ray Milkeyfd724402016-05-26 14:45:46 -0700366 intentsToApply.stream()
Thomas Vachuska2980c972016-02-23 20:58:49 -0800367 .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
Ray Milkeyfd724402016-05-26 14:45:46 -0700368 .forEach(contexts::add);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800369 }
370
371 // Builds the specified objective in the appropriate direction
372 private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
373 Direction direction) {
374 int size = intent.objectives().size();
375 List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
376 for (int i = 0; i < size; i++) {
377 DeviceId deviceId = intent.devices().get(i);
378 Objective.Builder builder = intent.objectives().get(i).copy();
379 FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
380
381 final Objective objective;
382 switch (direction) {
383 case ADD:
384 objective = builder.add(context);
385 break;
386 case REMOVE:
387 objective = builder.remove(context);
388 break;
389 default:
390 throw new UnsupportedOperationException("Unsupported direction " + direction);
391 }
392 context.setObjective(objective, deviceId);
393 contexts.add(context);
394 }
395 return contexts;
396 }
397
398 @Override
399 void apply() {
400 contexts.forEach(objectiveContext -> {
401 pendingContexts.add(objectiveContext);
402 flowObjectiveService.apply(objectiveContext.deviceId,
403 objectiveContext.objective);
404 });
405 }
406
407 @Override
408 public Object error() {
409 return errorContexts;
410 }
411
412 private class FlowObjectiveInstallationContext implements ObjectiveContext {
413 Objective objective;
414 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700415 ObjectiveError error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800416
417 void setObjective(Objective objective, DeviceId deviceId) {
418 this.objective = objective;
419 this.deviceId = deviceId;
420 }
421
422 @Override
423 public void onSuccess(Objective objective) {
424 finish();
425 }
426
427 @Override
428 public void onError(Objective objective, ObjectiveError error) {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700429 this.error = error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800430 errorContexts.add(this);
431 finish();
432 }
433
434 private void finish() {
435 synchronized (pendingContexts) {
436 pendingContexts.remove(this);
437 if (pendingContexts.isEmpty()) {
438 if (errorContexts.isEmpty()) {
439 successConsumer.accept(FlowObjectiveOperationContext.this);
440 } else {
441 errorConsumer.accept(FlowObjectiveOperationContext.this);
442 }
443 }
444 }
445 }
446
447 @Override
448 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700449 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800450 }
451 }
452 }
453
454 private class ErrorContext extends OperationContext {
455 @Override
456 void apply() {
457 throw new UnsupportedOperationException("Unsupported installable intent");
458 }
459
460 @Override
461 Object error() {
462 return null;
463 }
464
465 @Override
466 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
467 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800468 }
469}