blob: 8c9151e08b3d825627d88ff684051223327e733e [file] [log] [blame]
Yi Tsengc927a062017-05-02 15:02:37 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Yi Tsengc927a062017-05-02 15:02:37 -07003 *
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.installer;
18
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Sets;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070022import org.osgi.service.component.annotations.Activate;
23import org.osgi.service.component.annotations.Component;
24import org.osgi.service.component.annotations.Deactivate;
25import org.osgi.service.component.annotations.Reference;
26import org.osgi.service.component.annotations.ReferenceCardinality;
Yi Tsengc927a062017-05-02 15:02:37 -070027import org.onosproject.net.DeviceId;
28import org.onosproject.net.flowobjective.FilteringObjective;
29import org.onosproject.net.flowobjective.FlowObjectiveService;
30import org.onosproject.net.flowobjective.ForwardingObjective;
31import org.onosproject.net.flowobjective.NextObjective;
32import org.onosproject.net.flowobjective.Objective;
33import org.onosproject.net.flowobjective.ObjectiveContext;
34import org.onosproject.net.flowobjective.ObjectiveError;
35import org.onosproject.net.intent.FlowObjectiveIntent;
36import org.onosproject.net.intent.IntentData;
37import org.onosproject.net.intent.IntentExtensionService;
38import org.onosproject.net.intent.IntentInstallCoordinator;
39import org.onosproject.net.intent.IntentOperationContext;
40import org.onosproject.net.intent.IntentInstaller;
41import org.onosproject.net.intent.impl.IntentManager;
Yi Tseng24d9be72017-05-12 11:28:13 -070042import org.onosproject.net.intent.ObjectiveTrackerService;
Yi Tsengc927a062017-05-02 15:02:37 -070043import org.slf4j.Logger;
44
45import java.util.Collection;
46import java.util.List;
47import java.util.Objects;
48import java.util.Optional;
49import java.util.Set;
50import java.util.concurrent.atomic.AtomicInteger;
51
52import static org.onosproject.net.flowobjective.ObjectiveError.INSTALLATIONTHRESHOLDEXCEEDED;
53import static org.onosproject.net.intent.IntentInstaller.Direction.ADD;
54import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE;
55import static org.slf4j.LoggerFactory.getLogger;
56
57/**
58 * Installer for FlowObjectiveIntent.
59 */
60@Component(immediate = true)
61public class FlowObjectiveIntentInstaller implements IntentInstaller<FlowObjectiveIntent> {
62 private static final int OBJECTIVE_RETRY_THRESHOLD = 5;
63 private static final String UNSUPPORT_OBJ = "unsupported objective {}";
64 private final Logger log = getLogger(IntentManager.class);
65
Ray Milkeyd84f89b2018-08-17 14:54:17 -070066 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengc927a062017-05-02 15:02:37 -070067 protected IntentExtensionService intentExtensionService;
68
Ray Milkeyd84f89b2018-08-17 14:54:17 -070069 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengc927a062017-05-02 15:02:37 -070070 protected ObjectiveTrackerService trackerService;
71
Ray Milkeyd84f89b2018-08-17 14:54:17 -070072 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengc927a062017-05-02 15:02:37 -070073 protected IntentInstallCoordinator intentInstallCoordinator;
74
Ray Milkeyd84f89b2018-08-17 14:54:17 -070075 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengc927a062017-05-02 15:02:37 -070076 protected FlowObjectiveService flowObjectiveService;
77
78 @Activate
79 public void activate() {
80 intentExtensionService.registerInstaller(FlowObjectiveIntent.class, this);
81 }
82
83 @Deactivate
84 public void deactivated() {
85 intentExtensionService.unregisterInstaller(FlowObjectiveIntent.class);
86 }
87
88 @Override
89 public void apply(IntentOperationContext<FlowObjectiveIntent> intentOperationContext) {
90 Objects.requireNonNull(intentOperationContext);
91 Optional<IntentData> toUninstall = intentOperationContext.toUninstall();
92 Optional<IntentData> toInstall = intentOperationContext.toInstall();
93
94 List<FlowObjectiveIntent> uninstallIntents = intentOperationContext.intentsToUninstall();
95 List<FlowObjectiveIntent> installIntents = intentOperationContext.intentsToInstall();
96
97 if (!toInstall.isPresent() && !toUninstall.isPresent()) {
98 intentInstallCoordinator.intentInstallSuccess(intentOperationContext);
99 return;
100 }
101
102 if (toUninstall.isPresent()) {
103 IntentData intentData = toUninstall.get();
104 trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources());
105 uninstallIntents.forEach(installable ->
106 trackerService.removeTrackedResources(intentData.intent().key(),
107 installable.resources()));
108 }
109
110 if (toInstall.isPresent()) {
111 IntentData intentData = toInstall.get();
112 trackerService.addTrackedResources(intentData.key(), intentData.intent().resources());
113 installIntents.forEach(installable ->
114 trackerService.addTrackedResources(intentData.key(),
115 installable.resources()));
116 }
117
118 FlowObjectiveIntentInstallationContext intentInstallationContext =
119 new FlowObjectiveIntentInstallationContext(intentOperationContext);
120
121 uninstallIntents.stream()
122 .map(intent -> buildObjectiveContexts(intent, REMOVE))
123 .flatMap(Collection::stream)
124 .forEach(context -> {
125 context.intentInstallationContext(intentInstallationContext);
126 intentInstallationContext.addContext(context);
127 intentInstallationContext.addPendingContext(context);
128 });
129
130 installIntents.stream()
131 .map(intent -> buildObjectiveContexts(intent, ADD))
132 .flatMap(Collection::stream)
133 .forEach(context -> {
134 context.intentInstallationContext(intentInstallationContext);
135 intentInstallationContext.addContext(context);
136 intentInstallationContext.addNextPendingContext(context);
137 });
138
139 intentInstallationContext.apply();
140 }
141
142 /**
143 * Builds all objective contexts for a given flow objective Intent with given
144 * operation.
145 *
146 * @param intent the flow objective Intent
147 * @param direction the operation of this Intent
148 * @return all objective context of the Intent with given operation
149 */
150 private Set<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
151 Direction direction) {
152 Objects.requireNonNull(intent);
153 Objects.requireNonNull(direction);
154 Set<FlowObjectiveInstallationContext> contexts = Sets.newHashSet();
155 int size = intent.objectives().size();
156 List<Objective> objectives = intent.objectives();
157 List<DeviceId> deviceIds = intent.devices();
158
159 if (direction == ADD) {
160 // Install objectives
161 // The flow objective system will handle the installation order
162 for (int i = 0; i < size; i++) {
163 Objective objective = objectives.get(i);
164 DeviceId deviceId = deviceIds.get(i);
165 FlowObjectiveInstallationContext ctx = buildObjectiveContext(objective, deviceId, direction);
166 contexts.add(ctx);
167 }
168 return contexts;
169 } else {
170 // Uninstall objecitves
171 // we need to care about ordering here
172 // basic idea is to chain objective contexts
173 for (int i = 0; i < size; i++) {
174 Objective objective = intent.objectives().get(i);
175 DeviceId deviceId = intent.devices().get(i);
176 if (objective instanceof FilteringObjective) {
177 // don't need to care ordering of filtering objective
178 FlowObjectiveInstallationContext ctx =
179 buildObjectiveContext(objective, deviceId, direction);
180 contexts.add(ctx);
181 } else if (objective instanceof NextObjective) {
182 // need to removed after forwarding objective
183 // nothing to do here
184 } else if (objective instanceof ForwardingObjective) {
185 // forwarding objective, also find next objective if
186 // exist
187 FlowObjectiveInstallationContext fwdCtx =
188 buildObjectiveContext(objective, deviceId, direction);
189 ForwardingObjective fwd = (ForwardingObjective) objective;
190 NextObjective nxt = null;
191 Integer nextId = fwd.nextId();
192 if (nextId != null) {
193 for (int j = 0; j < size; j++) {
194 if (objectives.get(j).id() == nextId) {
195 nxt = (NextObjective) objectives.get(j);
196 break;
197 }
198 }
199 // if a next objective exists in the Intent
200 if (nxt != null) {
201 FlowObjectiveInstallationContext nxtCtx =
202 buildObjectiveContext(nxt, deviceId, direction);
203 fwdCtx.nextContext(nxtCtx);
204 }
205 }
206 contexts.add(fwdCtx);
207 } else {
208 // possible here?
209 log.warn(UNSUPPORT_OBJ, objective);
210 }
211 }
212 }
213 return contexts;
214 }
215
216 private FlowObjectiveInstallationContext buildObjectiveContext(Objective objective,
217 DeviceId deviceId,
218 Direction direction) {
219 Objects.requireNonNull(objective);
220 Objects.requireNonNull(deviceId);
221 Objects.requireNonNull(direction);
222 Objective.Builder builder = objective.copy();
223 FlowObjectiveInstallationContext ctx = new FlowObjectiveInstallationContext();
224 switch (direction) {
225 case ADD:
226 objective = builder.add(ctx);
227 break;
228 case REMOVE:
229 objective = builder.remove(ctx);
230 break;
231 default:
232 break;
233 }
234 ctx.setObjective(objective, deviceId);
235 return ctx;
236 }
237
238 /**
239 * Installation context for flow objective.
240 * Manages installation state of a flow objective.
241 */
242 class FlowObjectiveInstallationContext implements ObjectiveContext {
243 private Objective objective;
244 private DeviceId deviceId;
245 private ObjectiveError error;
246 private AtomicInteger retry;
247 private FlowObjectiveInstallationContext nextContext;
248 private FlowObjectiveIntentInstallationContext intentInstallationContext;
249
250 /**
251 * Set flow objective Intent installation context to this context.
252 *
253 * @param intentInstallationContext the Intent installation context
254 */
255 public void intentInstallationContext(FlowObjectiveIntentInstallationContext intentInstallationContext) {
256 Objects.requireNonNull(intentInstallationContext);
257 this.intentInstallationContext = intentInstallationContext;
258
259 // Set Intent installation context to the next context if exists.
260 if (nextContext != null) {
261 nextContext.intentInstallationContext(intentInstallationContext);
262 }
263 }
264
265 /**
266 * Sets next flow objective installation context.
267 *
268 * @param nextContext the next flow objective installation context
269 */
270 public void nextContext(FlowObjectiveInstallationContext nextContext) {
271 Objects.requireNonNull(nextContext);
272 this.nextContext = nextContext;
273 }
274
275 /**
276 * Sets objective and device id to this context; reset error states.
277 *
278 * @param objective the objective
279 * @param deviceId the device id
280 */
281 void setObjective(Objective objective, DeviceId deviceId) {
282 Objects.requireNonNull(objective);
283 Objects.requireNonNull(deviceId);
284 this.objective = objective;
285 this.deviceId = deviceId;
286 this.error = null;
287 this.retry = new AtomicInteger(0);
288 }
289
290 /**
291 * Gets the number of retries.
292 *
293 * @return the retry count
294 */
295 int retryTimes() {
296 return this.retry.get();
297 }
298
299 /**
300 * Increases the number of retries.
301 */
302 void increaseRetryValue() {
303 this.retry.incrementAndGet();
304 }
305
306 /**
307 * Completed this context.
308 *
309 * @param error the error of this context if exist; null otherwise
310 */
311 private void finished(ObjectiveError error) {
312 synchronized (intentInstallationContext) {
313 if (error != null) {
314 this.error = error;
315 intentInstallationContext.handleObjectiveError(this, error);
316 } else {
317 // apply next context if exist
318 if (nextContext != null) {
319 intentInstallationContext.addPendingContext(nextContext);
320 flowObjectiveService.apply(nextContext.deviceId,
321 nextContext.objective);
322 intentInstallationContext.removePendingContext(this);
323 } else {
324 intentInstallationContext.removePendingContext(this);
325 }
326 }
327 if (!intentInstallationContext.pendingContexts().isEmpty()) {
328 return;
329 }
330 // Apply second stage pending contexts if it is not empty
331 if (!intentInstallationContext.nextPendingContexts().isEmpty()) {
332 intentInstallationContext.moveNextPendingToPending();
333 final Set<ObjectiveContext> contextsToApply =
334 Sets.newHashSet(intentInstallationContext.pendingContexts());
335 contextsToApply.forEach(ctx -> {
336 FlowObjectiveInstallationContext foiCtx = (FlowObjectiveInstallationContext) ctx;
337 flowObjectiveService.apply(foiCtx.deviceId,
338 foiCtx.objective);
339 });
340 return;
341 }
Youngbo Sim3c933ec2018-07-17 11:32:47 +0900342 if (intentInstallationContext.errorContexts().isEmpty() && error == null) {
Yi Tsengc927a062017-05-02 15:02:37 -0700343 intentInstallCoordinator.intentInstallSuccess(intentInstallationContext.intentOperationContext());
Youngbo Sim3c933ec2018-07-17 11:32:47 +0900344 } else if (!intentInstallationContext.errorContexts().isEmpty()) {
Yi Tsengc927a062017-05-02 15:02:37 -0700345 intentInstallCoordinator.intentInstallFailed(intentInstallationContext.intentOperationContext());
Youngbo Sim3c933ec2018-07-17 11:32:47 +0900346 } else {
347 // waiting for error handling.
348 // if ctx.retryTimes() > OBJECTIVE_RETRY_THRESHOLD
349 // then handleObjectiveError will be executed.
Yi Tsengc927a062017-05-02 15:02:37 -0700350 }
351 }
352 }
353
354 @Override
355 public void onSuccess(Objective objective) {
356 finished(null);
357 }
358
359 @Override
360 public void onError(Objective objective, ObjectiveError error) {
361 finished(error);
362 }
363
364 @Override
365 public String toString() {
366 return String.format("(%s on %s for %s)", error, deviceId, objective);
367 }
368 }
369
370 /**
371 * Installation context for FlowObjective Intent.
372 * Manages states of pending and error flow objective contexts.
373 */
374 class FlowObjectiveIntentInstallationContext {
375 private final IntentOperationContext<FlowObjectiveIntent> intentOperationContext;
376 final List<ObjectiveContext> contexts = Lists.newArrayList();
377 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
378 final Set<ObjectiveContext> pendingContexts = Sets.newConcurrentHashSet();
379
380 // Second stage of pending contexts
381 final Set<ObjectiveContext> nextPendingContexts = Sets.newConcurrentHashSet();
382
383 /**
384 * Creates a flow objective installation context.
385 *
386 * @param intentOperationContext the flow objective installation context
387 */
388 public FlowObjectiveIntentInstallationContext(
389 IntentOperationContext<FlowObjectiveIntent> intentOperationContext) {
390 Objects.requireNonNull(intentOperationContext);
391 this.intentOperationContext = intentOperationContext;
392 }
393
394 /**
395 * Gets Intent operation context of this context.
396 *
397 * @return the Intent operation context
398 */
399 public IntentOperationContext<FlowObjectiveIntent> intentOperationContext() {
400 return intentOperationContext;
401 }
402
403 /**
404 * Applies all contexts to flow objective service.
405 */
406 public void apply() {
407 if (pendingContexts.isEmpty()) {
408 moveNextPendingToPending();
409 }
410 final Set<ObjectiveContext> contextsToApply = pendingContexts();
411 contextsToApply.forEach(ctx -> {
412 FlowObjectiveInstallationContext foiCtx =
413 (FlowObjectiveInstallationContext) ctx;
414 flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective);
415 });
416 }
417
418 /**
419 * Gets all error contexts.
420 *
421 * @return the error contexts
422 */
423 public Set<ObjectiveContext> errorContexts() {
424 return ImmutableSet.copyOf(errorContexts);
425 }
426
427 /**
428 * Gets all pending contexts.
429 *
430 * @return the pending contexts
431 */
432 public Set<ObjectiveContext> pendingContexts() {
433 return ImmutableSet.copyOf(pendingContexts);
434 }
435
436 /**
437 * Gets all pending contexts of next stage.
438 *
439 * @return the pending contexts for next stage
440 */
441 public Set<ObjectiveContext> nextPendingContexts() {
442 return ImmutableSet.copyOf(nextPendingContexts);
443 }
444
445 /**
446 * Adds a context.
447 *
448 * @param context the context
449 */
450 public void addContext(ObjectiveContext context) {
451 Objects.requireNonNull(context);
452 contexts.add(context);
453 }
454
455 /**
456 * Adds a context to pending context of next stage.
457 *
458 * @param context the context
459 */
460 public void addNextPendingContext(ObjectiveContext context) {
461 Objects.requireNonNull(context);
462 nextPendingContexts.add(context);
463 }
464
465 /**
466 * Adds a context to pending context.
467 *
468 * @param context the context
469 */
470 public void addPendingContext(ObjectiveContext context) {
471 Objects.requireNonNull(context);
472 pendingContexts.add(context);
473 }
474
475 /**
476 * Removes the pending context.
477 *
478 * @param context the context
479 */
480 public void removePendingContext(ObjectiveContext context) {
481 Objects.requireNonNull(context);
482 pendingContexts.remove(context);
483 }
484
485 /**
486 * Moves pending context from next stage to current stage.
487 */
488 public void moveNextPendingToPending() {
489 pendingContexts.addAll(nextPendingContexts);
490 nextPendingContexts.clear();
491 }
492
493 /**
494 * Handles error of objective context.
495 *
496 * @param ctx the objective context
497 * @param error the error
498 */
499 public void handleObjectiveError(FlowObjectiveInstallationContext ctx,
500 ObjectiveError error) {
501 Objects.requireNonNull(ctx);
502 Objects.requireNonNull(error);
503 log.debug("Got error(s) when install objective: {}, error: {}, retry: {}",
504 ctx.objective, ctx.error, ctx.retry);
505 if (ctx.retryTimes() > OBJECTIVE_RETRY_THRESHOLD) {
506 ctx.error = INSTALLATIONTHRESHOLDEXCEEDED;
507 pendingContexts.remove(ctx);
508 errorContexts.add(ctx);
509 return;
510 }
511 // reset error
512 ctx.error = null;
513 // strategies for errors
514 switch (error) {
515 case GROUPEXISTS:
516 if (ctx.objective.op() == Objective.Operation.ADD &&
517 ctx.objective instanceof NextObjective) {
518 // Next group exists
519 // build new objective with new op ADD_TO_EXIST
520 NextObjective newObj =
521 ((NextObjective.Builder) ctx.objective.copy()).addToExisting(ctx);
522 ctx.setObjective(newObj, ctx.deviceId);
523 ctx.increaseRetryValue();
524 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
525 } else {
526 pendingContexts.remove(ctx);
527 errorContexts.add(ctx);
528 }
529 break;
530 case GROUPINSTALLATIONFAILED:
531 // Group install failed, retry again
532 ctx.increaseRetryValue();
533 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
534 break;
535 case GROUPMISSING:
536 if (ctx.objective.op() == Objective.Operation.ADD_TO_EXISTING) {
537 // Next group not exist, but we want to add new buckets
538 // build new objective with new op ADD
539 NextObjective newObj = (NextObjective) ctx.objective.copy().add(ctx);
540 ctx.setObjective(newObj, ctx.deviceId);
541 ctx.increaseRetryValue();
542 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
543 } else if (ctx.objective.op() == Objective.Operation.REMOVE ||
544 ctx.objective.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
545 // Already removed, no need to do anything
546 ctx.error = null;
547 pendingContexts.remove(ctx);
548 return;
549 } else {
550 // Next chaining group missing, try again.
551 ctx.increaseRetryValue();
552 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
553 }
554 break;
555 case FLOWINSTALLATIONFAILED:
556 case GROUPREMOVALFAILED:
557 case INSTALLATIONTIMEOUT:
558 // Retry
559 ctx.increaseRetryValue();
560 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
561 break;
562 default:
563 pendingContexts.remove(ctx);
564 errorContexts.add(ctx);
565 break;
566 }
567 }
568 }
569}