blob: 7b12a079c0749f46017ec71df8e716fddac954c3 [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.net.intent.impl.installer;
import com.google.common.collect.Lists;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleOperationsContext;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentData;
import org.onosproject.net.intent.IntentExtensionService;
import org.onosproject.net.intent.IntentInstallCoordinator;
import org.onosproject.net.intent.IntentInstaller;
import org.onosproject.net.intent.IntentOperationContext;
import org.onosproject.net.intent.IntentStore;
import org.onosproject.net.intent.ObjectiveTrackerService;
import org.onosproject.net.intent.impl.IntentManager;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.intent.IntentInstaller.Direction.ADD;
import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE;
import static org.onosproject.net.intent.IntentState.INSTALLED;
import static org.onosproject.net.intent.IntentState.REALLOCATING;
import static org.onosproject.net.intent.constraint.NonDisruptiveConstraint.requireNonDisruptive;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Installer for FlowRuleIntent.
*/
@Component(immediate = true)
public class FlowRuleIntentInstaller implements IntentInstaller<FlowRuleIntent> {
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentExtensionService intentExtensionService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ObjectiveTrackerService trackerService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentInstallCoordinator intentInstallCoordinator;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ComponentConfigService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentStore store;
private ScheduledExecutorService nonDisruptiveIntentInstaller;
private static final int DEFAULT_NON_DISRUPTIVE_INSTALLATION_WAITING_TIME = 1;
//@Property(name = "nonDisruptiveInstallationWaitingTime",
// intValue = DEFAULT_NON_DISRUPTIVE_INSTALLATION_WAITING_TIME,
// label = "Number of seconds to wait during the non-disruptive installation phases")
private int nonDisruptiveInstallationWaitingTime = DEFAULT_NON_DISRUPTIVE_INSTALLATION_WAITING_TIME;
protected final Logger log = getLogger(IntentManager.class);
private boolean isReallocationStageFailed = false;
private static final LinkComparator LINK_COMPARATOR = new LinkComparator();
@Activate
public void activate() {
intentExtensionService.registerInstaller(FlowRuleIntent.class, this);
nonDisruptiveIntentInstaller =
newSingleThreadScheduledExecutor(groupedThreads("onos/intent", "non-disruptive-installer", log));
configService.registerProperties(getClass());
}
@Deactivate
public void deactivated() {
intentExtensionService.unregisterInstaller(FlowRuleIntent.class);
configService.unregisterProperties(getClass(), false);
}
@Modified
public void modified(ComponentContext context) {
if (context == null) {
nonDisruptiveInstallationWaitingTime = DEFAULT_NON_DISRUPTIVE_INSTALLATION_WAITING_TIME;
log.info("Restored default installation time for non-disruptive reallocation (1 sec.)");
return;
}
String s = Tools.get(context.getProperties(), "nonDisruptiveInstallationWaitingTime");
int nonDisruptiveTime = isNullOrEmpty(s) ? nonDisruptiveInstallationWaitingTime : Integer.parseInt(s);
if (nonDisruptiveTime != nonDisruptiveInstallationWaitingTime) {
nonDisruptiveInstallationWaitingTime = nonDisruptiveTime;
log.info("Reconfigured non-disruptive reallocation with installation delay {} sec.",
nonDisruptiveInstallationWaitingTime);
}
}
@Override
public void apply(IntentOperationContext<FlowRuleIntent> context) {
Optional<IntentData> toUninstall = context.toUninstall();
Optional<IntentData> toInstall = context.toInstall();
if (toInstall.isPresent() && toUninstall.isPresent()) {
Intent intentToInstall = toInstall.get().intent();
if (requireNonDisruptive(intentToInstall) && INSTALLED.equals(toUninstall.get().state())) {
reallocate(context);
return;
}
}
if (!toInstall.isPresent() && !toUninstall.isPresent()) {
// Nothing to do.
intentInstallCoordinator.intentInstallSuccess(context);
return;
}
List<FlowRuleIntent> uninstallIntents = context.intentsToUninstall();
List<FlowRuleIntent> installIntents = context.intentsToInstall();
List<FlowRule> flowRulesToUninstall;
List<FlowRule> flowRulesToInstall;
if (toUninstall.isPresent()) {
// Remove tracked resource from both Intent and installable Intents.
trackIntentResources(toUninstall.get(), uninstallIntents, REMOVE);
// Retrieves all flow rules from all flow rule Intents.
flowRulesToUninstall = uninstallIntents.stream()
.map(FlowRuleIntent::flowRules)
.flatMap(Collection::stream)
.collect(Collectors.toList());
} else {
// No flow rules to be uninstalled.
flowRulesToUninstall = Collections.emptyList();
}
if (toInstall.isPresent()) {
// Track resource from both Intent and installable Intents.
trackIntentResources(toInstall.get(), installIntents, ADD);
// Retrieves all flow rules from all flow rule Intents.
flowRulesToInstall = installIntents.stream()
.map(FlowRuleIntent::flowRules)
.flatMap(Collection::stream)
.collect(Collectors.toList());
} else {
// No flow rules to be installed.
flowRulesToInstall = Collections.emptyList();
}
List<FlowRule> flowRuleToModify;
List<FlowRule> dontTouch;
// If both uninstall/install list contained equal (=match conditions are equal) FlowRules,
// omit it from remove list, since it will/should be overwritten by install
flowRuleToModify = flowRulesToInstall.stream()
.filter(flowRule -> flowRulesToUninstall.stream().anyMatch(flowRule::equals))
.collect(Collectors.toList());
// If both contained exactMatch-ing FlowRules, remove from both list,
// since it will result in no-op.
dontTouch = flowRulesToInstall.stream()
.filter(flowRule -> flowRulesToUninstall.stream().anyMatch(flowRule::exactMatch))
.collect(Collectors.toList());
flowRulesToUninstall.removeAll(flowRuleToModify);
flowRulesToUninstall.removeAll(dontTouch);
flowRulesToInstall.removeAll(flowRuleToModify);
flowRulesToInstall.removeAll(dontTouch);
flowRuleToModify.removeAll(dontTouch);
if (flowRulesToInstall.isEmpty() && flowRulesToUninstall.isEmpty() && flowRuleToModify.isEmpty()) {
// There is no flow rules to install/uninstall
intentInstallCoordinator.intentInstallSuccess(context);
return;
}
FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
// Add flows
flowRulesToInstall.forEach(builder::add);
// Modify flows
flowRuleToModify.forEach(builder::modify);
// Remove flows
flowRulesToUninstall.forEach(builder::remove);
FlowRuleOperationsContext flowRuleOperationsContext = new FlowRuleOperationsContext() {
@Override
public void onSuccess(FlowRuleOperations ops) {
intentInstallCoordinator.intentInstallSuccess(context);
}
@Override
public void onError(FlowRuleOperations ops) {
intentInstallCoordinator.intentInstallFailed(context);
}
};
FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
log.debug("applying intent {} -> {} with {} rules: {}",
toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
toInstall.map(x -> x.key().toString()).orElse("<empty>"),
operations.stages().stream().mapToLong(Set::size).sum(),
operations.stages());
flowRuleService.apply(operations);
}
private void reallocate(IntentOperationContext<FlowRuleIntent> context) {
Optional<IntentData> toUninstall = context.toUninstall();
Optional<IntentData> toInstall = context.toInstall();
//TODO: Update the Intent store with this information
toInstall.get().setState(REALLOCATING);
store.write(toInstall.get());
List<FlowRuleIntent> uninstallIntents = Lists.newArrayList(context.intentsToUninstall());
List<FlowRuleIntent> installIntents = Lists.newArrayList(context.intentsToInstall());
FlowRuleOperations.Builder firstStageOperationsBuilder = FlowRuleOperations.builder();
List<FlowRule> secondStageFlowRules = Lists.newArrayList();
FlowRuleOperations.Builder thirdStageOperationsBuilder = FlowRuleOperations.builder();
FlowRuleOperations.Builder finalStageOperationsBuilder = FlowRuleOperations.builder();
prepareReallocation(uninstallIntents, installIntents,
firstStageOperationsBuilder, secondStageFlowRules,
thirdStageOperationsBuilder, finalStageOperationsBuilder);
trackIntentResources(toUninstall.get(), uninstallIntents, REMOVE);
trackIntentResources(toInstall.get(), installIntents, ADD);
CountDownLatch stageCompleteLatch = new CountDownLatch(1);
FlowRuleOperations firstStageOperations = firstStageOperationsBuilder
.build(new StageOperation(context, stageCompleteLatch));
flowRuleService.apply(firstStageOperations);
try {
stageCompleteLatch.await(nonDisruptiveInstallationWaitingTime, TimeUnit.SECONDS);
if (isReallocationStageFailed) {
log.error("Reallocation FAILED in stage one: the following FlowRuleOperations are not executed {}",
firstStageOperations);
return;
} else {
log.debug("Reallocation stage one completed");
}
} catch (Exception e) {
log.warn("Latch exception in the reallocation stage one");
}
for (FlowRule flowRule : secondStageFlowRules) {
stageCompleteLatch = new CountDownLatch(1);
FlowRuleOperations operations = FlowRuleOperations.builder()
.newStage()
.remove(flowRule)
.build(new StageOperation(context, stageCompleteLatch));
nonDisruptiveIntentInstaller.schedule(new NonDisruptiveInstallation(operations),
nonDisruptiveInstallationWaitingTime,
TimeUnit.SECONDS);
try {
stageCompleteLatch.await(nonDisruptiveInstallationWaitingTime, TimeUnit.SECONDS);
if (isReallocationStageFailed) {
log.error("Reallocation FAILED in stage two: " +
"the following FlowRuleOperations are not executed {}",
operations);
return;
} else {
log.debug("Reallocation stage two completed");
}
} catch (Exception e) {
log.warn("Latch exception in the reallocation stage two");
}
}
stageCompleteLatch = new CountDownLatch(1);
FlowRuleOperations thirdStageOperations = thirdStageOperationsBuilder
.build(new StageOperation(context, stageCompleteLatch));
nonDisruptiveIntentInstaller.schedule(new NonDisruptiveInstallation(thirdStageOperations),
nonDisruptiveInstallationWaitingTime,
TimeUnit.SECONDS);
try {
stageCompleteLatch.await(nonDisruptiveInstallationWaitingTime, TimeUnit.SECONDS);
if (isReallocationStageFailed) {
log.error("Reallocation FAILED in stage three: " +
"the following FlowRuleOperations are not executed {}",
thirdStageOperations);
return;
} else {
log.debug("Reallocation stage three completed");
}
} catch (Exception e) {
log.warn("Latch exception in the reallocation stage three");
}
FlowRuleOperationsContext flowRuleOperationsContext = new FlowRuleOperationsContext() {
@Override
public void onSuccess(FlowRuleOperations ops) {
intentInstallCoordinator.intentInstallSuccess(context);
log.info("Non-disruptive reallocation completed for intent {}", toInstall.get().key());
}
@Override
public void onError(FlowRuleOperations ops) {
intentInstallCoordinator.intentInstallFailed(context);
}
};
FlowRuleOperations finalStageOperations = finalStageOperationsBuilder.build(flowRuleOperationsContext);
flowRuleService.apply(finalStageOperations);
}
/**
* This method prepares the {@link FlowRule} required for every reallocation stage.
* <p>Stage 1: the FlowRules of the new path are installed,
* with a lower priority only on the devices shared with the old path;</p>
* <p>Stage 2: the FlowRules of the old path are removed from the ingress to the egress points,
* only in the shared devices;</p>
* <p>Stage 3: the FlowRules with a lower priority are restored to the original one;</p>
* <p>Stage 4: the remaining FlowRules of the old path are deleted.</p>
*
* @param uninstallIntents the previous FlowRuleIntent
* @param installIntents the new FlowRuleIntent to be installed
* @param firstStageBuilder the first stage operation builder
* @param secondStageFlowRules the second stage FlowRules
* @param thirdStageBuilder the third stage operation builder
* @param finalStageBuilder the last stage operation builder
*/
private void prepareReallocation(List<FlowRuleIntent> uninstallIntents, List<FlowRuleIntent> installIntents,
FlowRuleOperations.Builder firstStageBuilder,
List<FlowRule> secondStageFlowRules,
FlowRuleOperations.Builder thirdStageBuilder,
FlowRuleOperations.Builder finalStageBuilder) {
// Filter out same intents and intents with same flow rules
installIntents.forEach(installIntent -> {
uninstallIntents.forEach(uninstallIntent -> {
List<FlowRule> uninstallFlowRules = Lists.newArrayList(uninstallIntent.flowRules());
List<FlowRule> installFlowRules = Lists.newArrayList(installIntent.flowRules());
List<FlowRule> secondStageRules = Lists.newArrayList();
List<FlowRule> thirdStageRules = Lists.newArrayList();
List<DeviceId> orderedDeviceList = createIngressToEgressDeviceList(installIntent.resources());
uninstallIntent.flowRules().forEach(flowRuleToUnistall -> {
installIntent.flowRules().forEach(flowRuleToInstall -> {
if (flowRuleToInstall.exactMatch(flowRuleToUnistall)) {
//The FlowRules are in common (i.e., we are sharing the path)
uninstallFlowRules.remove(flowRuleToInstall);
installFlowRules.remove(flowRuleToInstall);
} else if (flowRuleToInstall.deviceId().equals(flowRuleToUnistall.deviceId())) {
//FlowRules that have a device in common but
// different treatment/selector (i.e., overlapping path)
FlowRule flowRuleWithLowerPriority = DefaultFlowRule.builder()
.withPriority(flowRuleToInstall.priority() - 1)
.withSelector(flowRuleToInstall.selector())
.forDevice(flowRuleToInstall.deviceId())
.makePermanent()
.withTreatment(flowRuleToInstall.treatment())
.fromApp(new DefaultApplicationId(flowRuleToInstall.appId(),
"org.onosproject.net.intent"))
.build();
//Update the FlowRule to be installed with one with a lower priority
installFlowRules.remove(flowRuleToInstall);
installFlowRules.add(flowRuleWithLowerPriority);
//Add the FlowRule to be uninstalled to the second stage of non-disruptive update
secondStageRules.add(flowRuleToUnistall);
uninstallFlowRules.remove(flowRuleToUnistall);
thirdStageRules.add(flowRuleToInstall);
uninstallFlowRules.add(flowRuleWithLowerPriority);
}
});
});
firstStageBuilder.newStage();
installFlowRules.forEach(firstStageBuilder::add);
Collections.sort(secondStageRules, new SecondStageComparator(orderedDeviceList));
secondStageFlowRules.addAll(secondStageRules);
thirdStageBuilder.newStage();
thirdStageRules.forEach(thirdStageBuilder::add);
finalStageBuilder.newStage();
uninstallFlowRules.forEach(finalStageBuilder::remove);
});
});
}
private class StageOperation implements FlowRuleOperationsContext {
private IntentOperationContext<FlowRuleIntent> context;
private CountDownLatch stageCompleteLatch;
public StageOperation(IntentOperationContext<FlowRuleIntent> context, CountDownLatch stageCompleteLatch) {
this.context = context;
this.stageCompleteLatch = stageCompleteLatch;
isReallocationStageFailed = false;
}
@Override
public void onSuccess(FlowRuleOperations ops) {
stageCompleteLatch.countDown();
log.debug("FlowRuleOperations correctly completed");
}
@Override
public void onError(FlowRuleOperations ops) {
intentInstallCoordinator.intentInstallFailed(context);
isReallocationStageFailed = true;
stageCompleteLatch.countDown();
log.debug("Installation error for {}", ops);
}
}
private final class SecondStageComparator implements Comparator<FlowRule> {
private List<DeviceId> deviceIds;
private SecondStageComparator(List<DeviceId> deviceIds) {
this.deviceIds = deviceIds;
}
@Override
public int compare(FlowRule o1, FlowRule o2) {
Integer index1 = deviceIds.indexOf(o1.deviceId());
Integer index2 = deviceIds.indexOf(o2.deviceId());
return index1.compareTo(index2);
}
}
/**
* Create a list of devices ordered from the ingress to the egress of a path.
* @param resources the resources of the intent
* @return a list of devices
*/
private List<DeviceId> createIngressToEgressDeviceList(Collection<NetworkResource> resources) {
List<DeviceId> deviceIds = Lists.newArrayList();
List<Link> links = Lists.newArrayList();
for (NetworkResource resource : resources) {
if (resource instanceof Link) {
Link linkToAdd = (Link) resource;
if (linkToAdd.type() != Link.Type.EDGE) {
links.add(linkToAdd);
}
}
}
Collections.sort(links, LINK_COMPARATOR);
int i = 0;
for (Link orderedLink : links) {
deviceIds.add(orderedLink.src().deviceId());
if (i == resources.size() - 1) {
deviceIds.add(orderedLink.dst().deviceId());
}
i++;
}
return deviceIds;
}
/**
* Compares two links in order to find which one is before or after the other.
*/
private static class LinkComparator implements Comparator<Link> {
@Override
public int compare(Link l1, Link l2) {
//l1 is before l2
if (l1.dst().deviceId() == l2.src().deviceId()) {
return -1;
}
//l1 is after l2
if (l1.src().deviceId() == l2.dst().deviceId()) {
return 1;
}
//l2 and l1 are not connected to a common device
return 0;
}
}
private final class NonDisruptiveInstallation implements Runnable {
private FlowRuleOperations op;
private NonDisruptiveInstallation(FlowRuleOperations op) {
this.op = op;
}
@Override
public void run() {
flowRuleService.apply(this.op);
}
}
/**
* Track or un-track network resource of a Intent and it's installable
* Intents.
*
* @param intentData the Intent data
* @param intentsToApply the list of flow rule Intents from the Intent
* @param direction the direction to determine track or un-track
*/
private void trackIntentResources(IntentData intentData, List<FlowRuleIntent> intentsToApply, Direction direction) {
switch (direction) {
case ADD:
trackerService.addTrackedResources(intentData.key(), intentData.intent().resources());
intentsToApply.forEach(installable ->
trackerService.addTrackedResources(intentData.key(),
installable.resources()));
break;
case REMOVE:
trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources());
intentsToApply.forEach(installable ->
trackerService.removeTrackedResources(intentData.intent().key(),
installable.resources()));
break;
default:
log.warn("Unknown resource tracking direction.");
break;
}
}
}