| /* |
| * 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.OsgiPropertyConstants.NON_DISRUPTIVE_INSTALLATION_WAITING_TIME; |
| import static org.onosproject.net.OsgiPropertyConstants.NON_DISRUPTIVE_INSTALLATION_WAITING_TIME_DEFAULT; |
| 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, |
| property = { |
| NON_DISRUPTIVE_INSTALLATION_WAITING_TIME + ":Integer=" + NON_DISRUPTIVE_INSTALLATION_WAITING_TIME_DEFAULT |
| } |
| ) |
| 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; |
| |
| /** Number of seconds to wait during the non-disruptive installation phases. */ |
| private int nonDisruptiveInstallationWaitingTime = NON_DISRUPTIVE_INSTALLATION_WAITING_TIME_DEFAULT; |
| |
| 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 = NON_DISRUPTIVE_INSTALLATION_WAITING_TIME_DEFAULT; |
| log.info("Restored default installation time for non-disruptive reallocation (1 sec.)"); |
| return; |
| } |
| |
| String s = Tools.get(context.getProperties(), NON_DISRUPTIVE_INSTALLATION_WAITING_TIME); |
| 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; |
| } |
| } |
| } |