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