/* * Copyright 2017-present Open Networking Laboratory * * 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.ImmutableSet; import com.google.common.collect.Sets; import org.apache.commons.lang3.tuple.Pair; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.onosproject.net.DeviceId; import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription; import org.onosproject.net.behaviour.protection.ProtectionConfig; import org.onosproject.net.config.NetworkConfigEvent; import org.onosproject.net.config.NetworkConfigListener; import org.onosproject.net.config.NetworkConfigService; import org.onosproject.net.intent.IntentData; import org.onosproject.net.intent.IntentException; import org.onosproject.net.intent.IntentExtensionService; import org.onosproject.net.intent.IntentInstallCoordinator; import org.onosproject.net.intent.IntentOperationContext; import org.onosproject.net.intent.IntentInstaller; import org.onosproject.net.intent.ProtectionEndpointIntent; import org.onosproject.net.intent.impl.IntentManager; import org.onosproject.net.intent.impl.ObjectiveTrackerService; import org.slf4j.Logger; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; import static org.onosproject.net.config.NetworkConfigEvent.Type.*; import static org.onosproject.net.intent.IntentInstaller.Direction.ADD; import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE; import static org.slf4j.LoggerFactory.getLogger; /** * Installer for ProtectionEndpointIntent. */ @Component(immediate = true) public class ProtectionEndpointIntentInstaller implements IntentInstaller<ProtectionEndpointIntent> { private static final String CONFIG_FAILED = "Config operation unsuccessful, expected %s, actual %s."; private final Logger log = getLogger(IntentManager.class); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentExtensionService intentExtensionService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) NetworkConfigService networkConfigService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) IntentInstallCoordinator intentInstallCoordinator; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) ObjectiveTrackerService trackerService; @Activate public void activate() { intentExtensionService.registerInstaller(ProtectionEndpointIntent.class, this); } @Deactivate public void deactivated() { intentExtensionService.unregisterInstaller(ProtectionEndpointIntent.class); } @Override public void apply(IntentOperationContext<ProtectionEndpointIntent> context) { Optional<IntentData> toUninstall = context.toUninstall(); Optional<IntentData> toInstall = context.toInstall(); List<ProtectionEndpointIntent> uninstallIntents = context.intentsToUninstall(); List<ProtectionEndpointIntent> installIntents = context.intentsToInstall(); if (!toInstall.isPresent() && !toUninstall.isPresent()) { intentInstallCoordinator.intentInstallSuccess(context); return; } if (toUninstall.isPresent()) { IntentData intentData = toUninstall.get(); trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources()); uninstallIntents.forEach(installable -> trackerService.removeTrackedResources(intentData.intent().key(), installable.resources())); } if (toInstall.isPresent()) { IntentData intentData = toInstall.get(); trackerService.addTrackedResources(intentData.key(), intentData.intent().resources()); installIntents.forEach(installable -> trackerService.addTrackedResources(intentData.key(), installable.resources())); } List<Stage> stages = new ArrayList<>(); stages.add(new Stage(uninstallIntents.stream() .map(i -> Pair.of(i, REMOVE)) .collect(Collectors.toList()))); stages.add(new Stage(installIntents.stream() .map(i -> Pair.of(i, ADD)) .collect(Collectors.toList()))); for (Stage stage : stages) { log.debug("applying Stage {}", stage); try { // wait for stage completion stage.apply(); stage.listeners().forEach(networkConfigService::removeListener); } catch (IntentException e) { log.error("Stage {} failed, reason: {}", stage, e.toString()); intentInstallCoordinator.intentInstallFailed(context); return; } } // All stage success intentInstallCoordinator.intentInstallSuccess(context); } /** * Stage of installable Intents which can be processed in parallel. */ private final class Stage { // should it have progress state, how far it went? private final Collection<Pair<ProtectionEndpointIntent, Direction>> ops; private final Set<NetworkConfigListener> listeners = Sets.newHashSet(); /** * Create a stage with given operations. * * @param ops the operations */ Stage(Collection<Pair<ProtectionEndpointIntent, Direction>> ops) { this.ops = checkNotNull(ops); } /** * Applies all operations for this stage. * * @return the CompletableFuture object for this operation */ void apply() { ops.stream() .map(op -> applyOp(op.getRight(), op.getLeft())) .forEach(future -> { try { future.get(100, TimeUnit.MILLISECONDS); } catch (TimeoutException | InterruptedException | ExecutionException e) { throw new IntentException(e.toString()); } }); } /** * Applies the protection endpoint Intent with a given direction. * * @param dir the direction * @param intent the protection endpoint Intent * @return the CompletableFuture object for this operation */ private CompletableFuture<Void> applyOp(Direction dir, ProtectionEndpointIntent intent) { log.trace("applying {}: {}", dir, intent); if (dir == REMOVE) { ProtectionConfigListener listener = new ProtectionConfigListener(ImmutableSet.of(CONFIG_REMOVED), intent.deviceId()); networkConfigService.addListener(listener); listeners.add(listener); networkConfigService.removeConfig(intent.deviceId(), ProtectionConfig.class); return listener.completableFuture(); } else { ProtectedTransportEndpointDescription description = intent.description(); // Can't do following. Will trigger empty CONFIG_ADDED //ProtectionConfig cfg = networkConfigService.addConfig(intent.deviceId(), // ProtectionConfig.class); ProtectionConfig cfg = new ProtectionConfig(intent.deviceId()); cfg.fingerprint(description.fingerprint()); cfg.peer(description.peer()); cfg.paths(description.paths()); ProtectionConfigListener listener = new ProtectionConfigListener(ImmutableSet.of(CONFIG_ADDED, CONFIG_UPDATED), intent.deviceId()); networkConfigService.addListener(listener); listeners.add(listener); networkConfigService.applyConfig(intent.deviceId(), ProtectionConfig.class, cfg.node()); return listener.completableFuture(); } } @Override public String toString() { return ops.toString(); } public Set<NetworkConfigListener> listeners() { return listeners; } /** * Listener for protection config for specific config event and device. */ class ProtectionConfigListener implements NetworkConfigListener { private CompletableFuture<Void> completableFuture; private Set<NetworkConfigEvent.Type> listenTypes; private DeviceId listenDevice; public ProtectionConfigListener(Set<NetworkConfigEvent.Type> listenTypes, DeviceId listenDevice) { completableFuture = new CompletableFuture<>(); this.listenTypes = listenTypes; this.listenDevice = listenDevice; } @Override public void event(NetworkConfigEvent event) { if (!event.subject().equals(listenDevice)) { return; } if (!listenTypes.contains(event.type())) { String errorMsg = String.format(CONFIG_FAILED, listenTypes.toString(), event.type()); completableFuture.completeExceptionally(new IntentException(errorMsg)); } else { completableFuture.complete(null); } } public CompletableFuture<Void> completableFuture() { return completableFuture; } } } }