blob: 68fc06e12b31a4738c0a4dc1c772f226444ef2a1 [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;
39import java.util.List;
40import java.util.Optional;
41import java.util.Set;
Thomas Vachuska2980c972016-02-23 20:58:49 -080042import java.util.function.Consumer;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080043import java.util.stream.Collectors;
44
Thomas Vachuska2980c972016-02-23 20:58:49 -080045import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080046import static org.onosproject.net.intent.IntentState.*;
47import static org.slf4j.LoggerFactory.getLogger;
48
49/**
50 * Auxiliary entity responsible for installing the intents into the environment.
51 */
52class IntentInstaller {
53
54 private static final Logger log = getLogger(IntentManager.class);
55
56 private IntentStore store;
57 private ObjectiveTrackerService trackerService;
58 private FlowRuleService flowRuleService;
59 private FlowObjectiveService flowObjectiveService;
60
61 private enum Direction {
62 ADD,
63 REMOVE
64 }
65
66 /**
67 * Initializes the installer with references to required services.
68 *
69 * @param intentStore intent store
70 * @param trackerService objective tracking service
71 * @param flowRuleService flow rule service
72 * @param flowObjectiveService flow objective service
73 */
74 void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
75 FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService) {
76 this.store = intentStore;
77 this.trackerService = trackerService;
78 this.flowRuleService = flowRuleService;
79 this.flowObjectiveService = flowObjectiveService;
80 }
81
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080082
83 // FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
Thomas Vachuska2980c972016-02-23 20:58:49 -080084 // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
85 // This will be addressed in intent domains work; not now.
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080086
87 /**
88 * Applies the specified intent updates to the environment by uninstalling
89 * and installing the intents and updating the store references appropriately.
90 *
91 * @param toUninstall optional intent to uninstall
92 * @param toInstall optional intent to install
93 */
94 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
Thomas Vachuska2980c972016-02-23 20:58:49 -080095 // Hook for handling success
96 Consumer<OperationContext> successConsumer = (ctx) -> {
97 if (toInstall.isPresent()) {
98 IntentData installData = toInstall.get();
99 log.debug("Completed installing: {}", installData.key());
100 installData.setState(INSTALLED);
101 store.write(installData);
102 } else if (toUninstall.isPresent()) {
103 IntentData uninstallData = toUninstall.get();
104 log.debug("Completed withdrawing: {}", uninstallData.key());
105 switch (uninstallData.request()) {
106 case INSTALL_REQ:
107 uninstallData.setState(FAILED);
108 break;
109 case WITHDRAW_REQ:
110 default: //TODO "default" case should not happen
111 uninstallData.setState(WITHDRAWN);
112 break;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800113 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800114 store.write(uninstallData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800115 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800116 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800117
Thomas Vachuska2980c972016-02-23 20:58:49 -0800118 // Hook for handling errors
119 Consumer<OperationContext> errorConsumer = (ctx) -> {
120 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
121 if (toInstall.isPresent()) {
122 IntentData installData = toInstall.get();
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700123 log.warn("Failed installation: {} {} due to {}",
Thomas Vachuska2980c972016-02-23 20:58:49 -0800124 installData.key(), installData.intent(), ctx.error());
125 installData.setState(CORRUPT);
126 installData.incrementErrorCount();
127 store.write(installData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800128 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800129 // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
130 if (toUninstall.isPresent()) {
131 IntentData uninstallData = toUninstall.get();
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700132 log.warn("Failed withdrawal: {} {} due to {}",
Thomas Vachuska2980c972016-02-23 20:58:49 -0800133 uninstallData.key(), uninstallData.intent(), ctx.error());
134 uninstallData.setState(CORRUPT);
135 uninstallData.incrementErrorCount();
136 store.write(uninstallData);
137 }
138 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800139
Thomas Vachuska2980c972016-02-23 20:58:49 -0800140 // Create a context for tracking the backing operations for applying
141 // the intents to the environment.
142 OperationContext context = createContext(toUninstall, toInstall);
143
144 context.prepare(toUninstall, toInstall, successConsumer, errorConsumer);
145 context.apply();
146 }
147
148 // ------ Utilities to support FlowRule vs. FlowObjective behavior -------
149
150 // Creates the context appropriate for tracking operations of the
151 // the specified intents.
152 private OperationContext createContext(Optional<IntentData> toUninstall,
153 Optional<IntentData> toInstall) {
154 if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
155 return new FlowRuleOperationContext();
156 }
157 if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
158 return new FlowObjectiveOperationContext();
159 }
160 return new ErrorContext();
161 }
162
163 private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
164 Class<? extends Intent> intentClass) {
165 boolean notBothNull = false;
166 if (toInstall.isPresent()) {
167 notBothNull = true;
168 if (!toInstall.get().installables().stream()
169 .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
170 return false;
171 }
172 }
173 if (toUninstall.isPresent()) {
174 notBothNull = true;
175 if (!toUninstall.get().installables().stream()
176 .allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
177 return false;
178 }
179 }
180 return notBothNull;
181 }
182
183 // Base context for applying and tracking operations related to installable intents.
184 private abstract class OperationContext {
185 protected Optional<IntentData> toUninstall;
186 protected Optional<IntentData> toInstall;
187 protected Consumer<OperationContext> successConsumer;
188 protected Consumer<OperationContext> errorConsumer;
189
190 abstract void apply();
191
192 abstract Object error();
193
194 abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
195
196 void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
197 Consumer<OperationContext> successConsumer,
198 Consumer<OperationContext> errorConsumer) {
199 this.toUninstall = toUninstall;
200 this.toInstall = toInstall;
201 this.successConsumer = successConsumer;
202 this.errorConsumer = errorConsumer;
203 prepareIntentData(toUninstall, Direction.REMOVE);
204 prepareIntentData(toInstall, Direction.ADD);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800205 }
206
Thomas Vachuska2980c972016-02-23 20:58:49 -0800207 /**
208 * Applies the specified intent data, if present, to the network using the
209 * specified context.
210 *
211 * @param intentData optional intent data; no-op if not present
212 * @param direction indicates adding or removal
213 */
214 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
215 if (!intentData.isPresent()) {
216 return;
217 }
218
219 IntentData data = intentData.get();
220 List<Intent> intentsToApply = data.installables();
221 checkState(intentsToApply.stream().allMatch(this::isSupported),
222 "Unsupported installable intents detected");
223
224 if (direction == Direction.ADD) {
225 trackerService.addTrackedResources(data.key(), data.intent().resources());
226 intentsToApply.forEach(installable ->
227 trackerService.addTrackedResources(data.key(),
228 installable.resources()));
229 } else {
230 trackerService.removeTrackedResources(data.key(), data.intent().resources());
231 intentsToApply.forEach(installable ->
232 trackerService.removeTrackedResources(data.intent().key(),
233 installable.resources()));
234 }
235
236 prepareIntents(intentsToApply, direction);
237 }
238
239 private boolean isSupported(Intent intent) {
240 return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent;
241 }
242 }
243
244
245 // Context for applying and tracking operations related to flow rule intent.
246 private class FlowRuleOperationContext extends OperationContext {
247 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
248 FlowRuleOperationsContext flowRuleOperationsContext;
249
250 void apply() {
251 flowRuleOperationsContext = new FlowRuleOperationsContext() {
252 @Override
253 public void onSuccess(FlowRuleOperations ops) {
254 successConsumer.accept(FlowRuleOperationContext.this);
255 }
256
257 @Override
258 public void onError(FlowRuleOperations ops) {
259 errorConsumer.accept(FlowRuleOperationContext.this);
260 }
261 };
262 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
263
264 if (log.isTraceEnabled()) {
265 log.trace("applying intent {} -> {} with {} rules: {}",
266 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
267 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
268 operations.stages().stream().mapToLong(Set::size).sum(),
269 operations.stages());
270 }
271
272 flowRuleService.apply(operations);
273 }
274
275 @Override
276 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
277 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
278 builder.newStage();
279
280 List<Collection<FlowRule>> stages = intentsToApply.stream()
281 .map(x -> (FlowRuleIntent) x)
282 .map(FlowRuleIntent::flowRules)
283 .collect(Collectors.toList());
284
285 for (Collection<FlowRule> rules : stages) {
286 if (direction == Direction.ADD) {
287 rules.forEach(builder::add);
288 } else {
289 rules.forEach(builder::remove);
290 }
291 }
292
293 }
294
295 @Override
296 public Object error() {
297 return flowRuleOperationsContext;
298 }
299 }
300
301 // Context for applying and tracking operations related to flow objective intents.
302 private class FlowObjectiveOperationContext extends OperationContext {
Ray Milkeyfd724402016-05-26 14:45:46 -0700303 List<FlowObjectiveInstallationContext> contexts = Lists.newLinkedList();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800304 final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
305 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
306
307 @Override
308 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Ray Milkeyfd724402016-05-26 14:45:46 -0700309 intentsToApply.stream()
Thomas Vachuska2980c972016-02-23 20:58:49 -0800310 .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
Ray Milkeyfd724402016-05-26 14:45:46 -0700311 .forEach(contexts::add);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800312 }
313
314 // Builds the specified objective in the appropriate direction
315 private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
316 Direction direction) {
317 int size = intent.objectives().size();
318 List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
319 for (int i = 0; i < size; i++) {
320 DeviceId deviceId = intent.devices().get(i);
321 Objective.Builder builder = intent.objectives().get(i).copy();
322 FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
323
324 final Objective objective;
325 switch (direction) {
326 case ADD:
327 objective = builder.add(context);
328 break;
329 case REMOVE:
330 objective = builder.remove(context);
331 break;
332 default:
333 throw new UnsupportedOperationException("Unsupported direction " + direction);
334 }
335 context.setObjective(objective, deviceId);
336 contexts.add(context);
337 }
338 return contexts;
339 }
340
341 @Override
342 void apply() {
343 contexts.forEach(objectiveContext -> {
344 pendingContexts.add(objectiveContext);
345 flowObjectiveService.apply(objectiveContext.deviceId,
346 objectiveContext.objective);
347 });
348 }
349
350 @Override
351 public Object error() {
352 return errorContexts;
353 }
354
355 private class FlowObjectiveInstallationContext implements ObjectiveContext {
356 Objective objective;
357 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700358 ObjectiveError error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800359
360 void setObjective(Objective objective, DeviceId deviceId) {
361 this.objective = objective;
362 this.deviceId = deviceId;
363 }
364
365 @Override
366 public void onSuccess(Objective objective) {
367 finish();
368 }
369
370 @Override
371 public void onError(Objective objective, ObjectiveError error) {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700372 this.error = error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800373 errorContexts.add(this);
374 finish();
375 }
376
377 private void finish() {
378 synchronized (pendingContexts) {
379 pendingContexts.remove(this);
380 if (pendingContexts.isEmpty()) {
381 if (errorContexts.isEmpty()) {
382 successConsumer.accept(FlowObjectiveOperationContext.this);
383 } else {
384 errorConsumer.accept(FlowObjectiveOperationContext.this);
385 }
386 }
387 }
388 }
389
390 @Override
391 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700392 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800393 }
394 }
395 }
396
397 private class ErrorContext extends OperationContext {
398 @Override
399 void apply() {
400 throw new UnsupportedOperationException("Unsupported installable intent");
401 }
402
403 @Override
404 Object error() {
405 return null;
406 }
407
408 @Override
409 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
410 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800411 }
412}