/* * Copyright 2016-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.segmentrouting.pwaas; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.commons.lang3.RandomUtils; import org.onlab.packet.Ethernet; import org.onlab.packet.MacAddress; import org.onlab.packet.MplsLabel; import org.onlab.packet.VlanId; import org.onlab.util.KryoNamespace; import org.onosproject.net.ConnectPoint; import org.onosproject.net.DeviceId; import org.onosproject.net.DisjointPath; import org.onosproject.net.Link; import org.onosproject.net.PortNumber; import org.onosproject.net.config.NetworkConfigEvent; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flowobjective.DefaultFilteringObjective; import org.onosproject.net.flowobjective.DefaultForwardingObjective; import org.onosproject.net.flowobjective.DefaultNextObjective; import org.onosproject.net.flowobjective.DefaultObjectiveContext; import org.onosproject.net.flowobjective.FilteringObjective; import org.onosproject.net.flowobjective.ForwardingObjective; import org.onosproject.net.flowobjective.NextObjective; import org.onosproject.net.flowobjective.Objective; import org.onosproject.net.flowobjective.ObjectiveContext; import org.onosproject.net.flowobjective.ObjectiveError; import org.onosproject.segmentrouting.SegmentRoutingManager; import org.onosproject.segmentrouting.SegmentRoutingService; import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException; import org.onosproject.segmentrouting.config.PwaasConfig; import org.onosproject.store.serializers.KryoNamespaces; import org.onosproject.store.service.ConsistentMap; import org.onosproject.store.service.Serializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; import static org.onosproject.net.flowobjective.ForwardingObjective.Flag.VERSATILE; import static org.onosproject.segmentrouting.pwaas.L2Mode.TAGGED; import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.INITIATION; import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.TERMINATION; import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Result.*; import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Direction.FWD; import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Direction.REV; /** * Handles pwaas related events. */ public class L2TunnelHandler { private static final Logger log = LoggerFactory.getLogger(L2TunnelHandler.class); /** * Error message for invalid paths. */ private static final String WRONG_TOPOLOGY = "Path in leaf-spine topology" + " should always be two hops: "; private final SegmentRoutingManager srManager; /** * To store the next objectives related to the initiation. */ private final ConsistentMap<String, NextObjective> l2InitiationNextObjStore; /** * To store the next objectives related to the termination. */ private final ConsistentMap<String, NextObjective> l2TerminationNextObjStore; /** * TODO a proper store is necessary to handle the policies, collisions and recovery. * We should have a proper store for the policies and the tunnels. For several reasons: * 1) We should avoid the overlapping of different policies; * 2) We should avoid the overlapping of different tunnels; * 3) We should have a proper mechanism for the protection; * The most important one is 3). At least for 3.0 EA0 was not possible * to remove the bucket, so we need a mapping between policies and tunnel * in order to proper update the fwd objective for the recovery of a fault. */ private final KryoNamespace.Builder l2TunnelKryo; /** * Create a l2 tunnel handler for the deploy and * for the tear down of pseudo wires. * * @param segmentRoutingManager the segment routing manager */ public L2TunnelHandler(SegmentRoutingManager segmentRoutingManager) { srManager = segmentRoutingManager; l2TunnelKryo = new KryoNamespace.Builder() .register(KryoNamespaces.API); l2InitiationNextObjStore = srManager.storageService .<String, NextObjective>consistentMapBuilder() .withName("onos-l2initiation-nextobj-store") .withSerializer(Serializer.using(l2TunnelKryo.build())) .build(); l2TerminationNextObjStore = srManager.storageService .<String, NextObjective>consistentMapBuilder() .withName("onos-l2termination-nextobj-store") .withSerializer(Serializer.using(l2TunnelKryo.build())) .build(); } /** * Processes Pwaas Config added event. * * @param event network config add event */ public void processPwaasConfigAdded(NetworkConfigEvent event) { log.info("Processing Pwaas CONFIG_ADDED"); PwaasConfig config = (PwaasConfig) event.config().get(); Set<DefaultL2TunnelDescription> pwToAdd = config.getPwIds() .stream() .map(config::getPwDescription) .collect(Collectors.toSet()); // We deploy all the pseudo wire deployed deploy(pwToAdd); } /** * To deploy a number of pseudo wires. * * @param pwToAdd the set of pseudo wires to add */ private void deploy(Set<DefaultL2TunnelDescription> pwToAdd) { Result result; long l2TunnelId; for (DefaultL2TunnelDescription currentL2Tunnel : pwToAdd) { l2TunnelId = currentL2Tunnel.l2Tunnel().tunnelId(); // The tunnel id cannot be 0. if (l2TunnelId == 0) { log.warn("Tunnel id id must be > 0"); continue; } // We do a sanity check of the pseudo wire. result = verifyPseudoWire(currentL2Tunnel); if (result != SUCCESS) { continue; } // We establish the tunnel. result = deployPseudoWireInit( currentL2Tunnel.l2Tunnel(), currentL2Tunnel.l2TunnelPolicy().cP1(), currentL2Tunnel.l2TunnelPolicy().cP2(), FWD ); if (result != SUCCESS) { continue; } // We create the policy. result = deployPolicy( l2TunnelId, currentL2Tunnel.l2TunnelPolicy().cP1(), currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(), currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(), result.nextId ); if (result != SUCCESS) { continue; } // We terminate the tunnel result = deployPseudoWireTerm( currentL2Tunnel.l2Tunnel(), currentL2Tunnel.l2TunnelPolicy().cP2(), currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(), FWD ); if (result != SUCCESS) { continue; } // We establish the reverse tunnel. result = deployPseudoWireInit( currentL2Tunnel.l2Tunnel(), currentL2Tunnel.l2TunnelPolicy().cP2(), currentL2Tunnel.l2TunnelPolicy().cP1(), REV ); if (result != SUCCESS) { continue; } result = deployPolicy( l2TunnelId, currentL2Tunnel.l2TunnelPolicy().cP2(), currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(), currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(), result.nextId ); if (result != SUCCESS) { continue; } deployPseudoWireTerm( currentL2Tunnel.l2Tunnel(), currentL2Tunnel.l2TunnelPolicy().cP1(), currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(), REV ); } } /** * Processes PWaaS Config updated event. * * @param event network config updated event */ public void processPwaasConfigUpdated(NetworkConfigEvent event) { log.info("Processing Pwaas CONFIG_UPDATED"); // We retrieve the old pseudo wires. PwaasConfig prevConfig = (PwaasConfig) event.prevConfig().get(); Set<Long> prevPws = prevConfig.getPwIds(); // We retrieve the new pseudo wires. PwaasConfig config = (PwaasConfig) event.config().get(); Set<Long> newPws = config.getPwIds(); // We compute the pseudo wires to update. Set<Long> updPws = newPws.stream() .filter(tunnelId -> prevPws.contains(tunnelId) && !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId))) .collect(Collectors.toSet()); // The pseudo wires to remove. Set<DefaultL2TunnelDescription> pwToRemove = prevPws.stream() .filter(tunnelId -> !newPws.contains(tunnelId)) .map(prevConfig::getPwDescription) .collect(Collectors.toSet()); tearDown(pwToRemove); // The pseudo wires to add. Set<DefaultL2TunnelDescription> pwToAdd = newPws.stream() .filter(tunnelId -> !prevPws.contains(tunnelId)) .map(config::getPwDescription) .collect(Collectors.toSet()); deploy(pwToAdd); // The pseudo wires to update. updPws.forEach(tunnelId -> updatePw( prevConfig.getPwDescription(tunnelId), config.getPwDescription(tunnelId)) ); } /** * Helper function to update a pw. * * @param oldPw the pseudo wire to remove * @param newPw the pseudo wirte to add */ private void updatePw(DefaultL2TunnelDescription oldPw, DefaultL2TunnelDescription newPw) { long tunnelId = oldPw.l2Tunnel().tunnelId(); // The async tasks to orchestrate the next and // forwarding update. CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>(); CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>(); CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>(); CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>(); CompletableFuture<ObjectiveError> fwdPwFuture = new CompletableFuture<>(); CompletableFuture<ObjectiveError> revPwFuture = new CompletableFuture<>(); Result result = verifyPseudoWire(newPw); if (result != SUCCESS) { return; } // First we remove both policy. log.debug("Start deleting fwd policy for {}", tunnelId); deletePolicy( tunnelId, oldPw.l2TunnelPolicy().cP1(), oldPw.l2TunnelPolicy().cP1InnerTag(), oldPw.l2TunnelPolicy().cP1OuterTag(), fwdInitNextFuture, FWD ); log.debug("Start deleting rev policy for {}", tunnelId); deletePolicy( tunnelId, oldPw.l2TunnelPolicy().cP2(), oldPw.l2TunnelPolicy().cP2InnerTag(), oldPw.l2TunnelPolicy().cP2OuterTag(), revInitNextFuture, REV ); // Finally we remove both the tunnels. fwdInitNextFuture.thenAcceptAsync(status -> { if (status == null) { log.debug("Fwd policy removed. Now remove fwd {} for {}", INITIATION, tunnelId); tearDownPseudoWireInit( tunnelId, oldPw.l2TunnelPolicy().cP1(), fwdTermNextFuture, FWD ); } }); revInitNextFuture.thenAcceptAsync(status -> { if (status == null) { log.debug("Rev policy removed. Now remove rev {} for {}", INITIATION, tunnelId); tearDownPseudoWireInit( tunnelId, oldPw.l2TunnelPolicy().cP2(), revTermNextFuture, REV ); } }); fwdTermNextFuture.thenAcceptAsync(status -> { if (status == null) { log.debug("Fwd {} removed. Now remove fwd {} for {}", INITIATION, TERMINATION, tunnelId); tearDownPseudoWireTerm( oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP2(), fwdPwFuture, FWD ); } }); revTermNextFuture.thenAcceptAsync(status -> { if (status == null) { log.debug("Rev {} removed. Now remove rev {} for {}", INITIATION, TERMINATION, tunnelId); tearDownPseudoWireTerm( oldPw.l2Tunnel(), oldPw.l2TunnelPolicy().cP1(), revPwFuture, REV ); } }); // At the end we install the new pw. fwdPwFuture.thenAcceptAsync(status -> { if (status == null) { log.debug("Deploying new fwd pw for {}", tunnelId); Result lamdaResult = deployPseudoWireInit( newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP1(), newPw.l2TunnelPolicy().cP2(), FWD ); if (lamdaResult != SUCCESS) { return; } lamdaResult = deployPolicy( tunnelId, newPw.l2TunnelPolicy().cP1(), newPw.l2TunnelPolicy().cP1InnerTag(), newPw.l2TunnelPolicy().cP1OuterTag(), lamdaResult.nextId ); if (lamdaResult != SUCCESS) { return; } deployPseudoWireTerm( newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP2(), newPw.l2TunnelPolicy().cP2OuterTag(), FWD ); } }); revPwFuture.thenAcceptAsync(status -> { if (status == null) { log.debug("Deploying new rev pw for {}", tunnelId); Result lamdaResult = deployPseudoWireInit( newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP2(), newPw.l2TunnelPolicy().cP1(), REV ); if (lamdaResult != SUCCESS) { return; } lamdaResult = deployPolicy( tunnelId, newPw.l2TunnelPolicy().cP2(), newPw.l2TunnelPolicy().cP2InnerTag(), newPw.l2TunnelPolicy().cP2OuterTag(), lamdaResult.nextId ); if (lamdaResult != SUCCESS) { return; } deployPseudoWireTerm( newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP1(), newPw.l2TunnelPolicy().cP1OuterTag(), REV ); } }); } /** * Processes Pwaas Config removed event. * * @param event network config removed event */ public void processPwaasConfigRemoved(NetworkConfigEvent event) { log.info("Processing Pwaas CONFIG_REMOVED"); PwaasConfig config = (PwaasConfig) event.prevConfig().get(); Set<DefaultL2TunnelDescription> pwToRemove = config.getPwIds() .stream() .map(config::getPwDescription) .collect(Collectors.toSet()); // We teardown all the pseudo wire deployed tearDown(pwToRemove); } /** * Helper function to handle the pw removal. * * @param pwToRemove the pseudo wires to remove */ private void tearDown(Set<DefaultL2TunnelDescription> pwToRemove) { Result result; long l2TunnelId; // We remove all the pw in the configuration file. for (DefaultL2TunnelDescription currentL2Tunnel : pwToRemove) { l2TunnelId = currentL2Tunnel.l2Tunnel().tunnelId(); if (l2TunnelId == 0) { log.warn("Tunnel id cannot be 0"); continue; } result = verifyPseudoWire(currentL2Tunnel); if (result != SUCCESS) { continue; } // First all we have to delete the policy. deletePolicy( l2TunnelId, currentL2Tunnel.l2TunnelPolicy().cP1(), currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(), currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(), null, FWD ); // Finally we will tear down the pseudo wire. tearDownPseudoWireInit( l2TunnelId, currentL2Tunnel.l2TunnelPolicy().cP1(), null, FWD ); tearDownPseudoWireTerm( currentL2Tunnel.l2Tunnel(), currentL2Tunnel.l2TunnelPolicy().cP2(), null, FWD ); // We do the same operations on the reverse side. deletePolicy( l2TunnelId, currentL2Tunnel.l2TunnelPolicy().cP2(), currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(), currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(), null, REV ); tearDownPseudoWireInit( l2TunnelId, currentL2Tunnel.l2TunnelPolicy().cP2(), null, REV ); tearDownPseudoWireTerm( currentL2Tunnel.l2Tunnel(), currentL2Tunnel.l2TunnelPolicy().cP1(), null, REV ); } } /** * Helper method to verify the integrity of the pseudo wire. * * @param l2TunnelDescription the pseudo wire description * @return the result of the check */ private Result verifyPseudoWire(DefaultL2TunnelDescription l2TunnelDescription) { Result result; DefaultL2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel(); DefaultL2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy(); result = verifyTunnel(l2Tunnel); if (result != SUCCESS) { log.warn("Tunnel {}: did not pass the validation", l2Tunnel.tunnelId()); return result; } result = verifyPolicy( l2TunnelPolicy.isAllVlan(), l2TunnelPolicy.cP1InnerTag(), l2TunnelPolicy.cP1OuterTag(), l2TunnelPolicy.cP2InnerTag(), l2TunnelPolicy.cP2OuterTag() ); if (result != SUCCESS) { log.warn("Policy for tunnel {}: did not pass the validation", l2Tunnel.tunnelId()); return result; } return SUCCESS; } /** * Handles the policy establishment which consists in * create the filtering and forwarding objectives related * to the initiation and termination. * * @param tunnelId the tunnel id * @param ingress the ingress point * @param ingressInner the ingress inner tag * @param ingressOuter the ingress outer tag * @param nextId the next objective id * @return the result of the operation */ private Result deployPolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner, VlanId ingressOuter, int nextId) { if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) { log.info("Abort creation of policy for tunnel {}: I am not the master", tunnelId); return SUCCESS; } List<Objective> objectives = Lists.newArrayList(); // We create the forwarding objective for supporting // the l2 tunnel. ForwardingObjective.Builder fwdBuilder = createInitFwdObjective( tunnelId, ingress.port(), nextId ); // We create and add objective context. ObjectiveContext context = new DefaultObjectiveContext( (objective) -> log.debug("FwdObj for tunnel {} populated", tunnelId), (objective, error) -> log.warn("Failed to populate fwdrObj for tunnel {}", tunnelId, error)); objectives.add(fwdBuilder.add(context)); // We create the filtering objective to define the // permit traffic in the switch FilteringObjective.Builder filtBuilder = createFiltObjective( ingress.port(), ingressInner, ingressOuter ); // We add the metadata. TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder() .setTunnelId(tunnelId); filtBuilder.withMeta(treatment.build()); // We create and add objective context. context = new DefaultObjectiveContext( (objective) -> log.debug("FilterObj for tunnel {} populated", tunnelId), (objective, error) -> log.warn("Failed to populate filterObj for tunnel {}", tunnelId, error)); objectives.add(filtBuilder.add(context)); for (Objective objective : objectives) { if (objective instanceof ForwardingObjective) { srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective); log.debug("Creating new FwdObj for initiation NextObj with id={} for tunnel {}", nextId, tunnelId); } else { srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective); log.debug("Creating new FiltObj for tunnel {}", tunnelId); } } return SUCCESS; } /** * Helper method to verify if the policy is whether or not * supported. * * @param isAllVlan all vlan mode * @param ingressInner the ingress inner tag * @param ingressOuter the ingress outer tag * @param egressInner the egress inner tag * @param egressOuter the egress outer tag * @return the result of verification */ private Result verifyPolicy(boolean isAllVlan, VlanId ingressInner, VlanId ingressOuter, VlanId egressInner, VlanId egressOuter) { // AllVlan mode is not supported yet. if (isAllVlan) { log.warn("AllVlan not supported yet"); return UNSUPPORTED; } // The vlan tags for cP1 and cP2 have to be different from // vlan none. if (ingressInner.equals(VlanId.NONE) || ingressOuter.equals(VlanId.NONE) || egressInner.equals(VlanId.NONE) || egressOuter.equals(VlanId.NONE)) { log.warn("The vlan tags for the connect point have to be" + "different from vlan none"); return WRONG_PARAMETERS; } return SUCCESS; } /** * Handles the tunnel establishment which consists in * create the next objectives related to the initiation. * * @param l2Tunnel the tunnel to deploy * @param ingress the ingress connect point * @param egress the egress connect point * @param direction the direction of the pw * @return the result of the operation */ private Result deployPseudoWireInit(DefaultL2Tunnel l2Tunnel, ConnectPoint ingress, ConnectPoint egress, Direction direction) { if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) { log.info("Abort initiation of tunnel {}: I am not the master", l2Tunnel.tunnelId()); return SUCCESS; } // We need at least a path between ingress and egress. Link nextHop = getNextHop(ingress, egress); if (nextHop == null) { log.warn("No path between ingress and egress"); return WRONG_PARAMETERS; } // We create the next objective without the metadata // context and id. We check if it already exists in the // store. If not we store as it is in the store. NextObjective.Builder nextObjectiveBuilder = createNextObjective( INITIATION, nextHop.src(), nextHop.dst(), l2Tunnel, egress.deviceId() ); if (nextObjectiveBuilder == null) { return INTERNAL_ERROR; } // We set the metadata. We will use this metadata // to inform the driver we are doing a l2 tunnel. TrafficSelector metadata = DefaultTrafficSelector .builder() .matchTunnelId(l2Tunnel.tunnelId()) .build(); nextObjectiveBuilder.withMeta(metadata); int nextId = srManager.flowObjectiveService.allocateNextId(); if (nextId < 0) { log.warn("Not able to allocate a next id for initiation"); return INTERNAL_ERROR; } nextObjectiveBuilder.withId(nextId); String key = generateKey(l2Tunnel.tunnelId(), direction); l2InitiationNextObjStore.put(key, nextObjectiveBuilder.add()); ObjectiveContext context = new DefaultObjectiveContext( (objective) -> log.debug("Initiation l2 tunnel rule for {} populated", l2Tunnel.tunnelId()), (objective, error) -> log.warn("Failed to populate Initiation l2 tunnel rule for {}: {}", l2Tunnel.tunnelId(), error)); NextObjective nextObjective = nextObjectiveBuilder.add(context); srManager.flowObjectiveService.next(ingress.deviceId(), nextObjective); log.debug("Initiation next objective for {} not found. Creating new NextObj with id={}", l2Tunnel.tunnelId(), nextObjective.id() ); Result result = SUCCESS; result.nextId = nextObjective.id(); return result; } /** * Handles the tunnel termination, which consists in the creation * of a forwarding objective and a next objective. * * @param l2Tunnel the tunnel to terminate * @param egress the egress point * @param egressVlan the expected vlan at egress * @param direction the direction * @return the result of the operation */ private Result deployPseudoWireTerm(DefaultL2Tunnel l2Tunnel, ConnectPoint egress, VlanId egressVlan, Direction direction) { // We create the group relative to the termination. // It's fine to abort the termination if we are // not the master. if (!srManager.mastershipService.isLocalMaster(egress.deviceId())) { log.info("Abort termination of tunnel {}: I am not the master", l2Tunnel.tunnelId()); return SUCCESS; } NextObjective.Builder nextObjectiveBuilder = createNextObjective( TERMINATION, egress, null, null, egress.deviceId() ); if (nextObjectiveBuilder == null) { return INTERNAL_ERROR; } TrafficSelector metadata = DefaultTrafficSelector .builder() .matchVlanId(egressVlan) .build(); nextObjectiveBuilder.withMeta(metadata); int nextId = srManager.flowObjectiveService.allocateNextId(); if (nextId < 0) { log.warn("Not able to allocate a next id for initiation"); return INTERNAL_ERROR; } nextObjectiveBuilder.withId(nextId); String key = generateKey(l2Tunnel.tunnelId(), direction); l2TerminationNextObjStore.put(key, nextObjectiveBuilder.add()); ObjectiveContext context = new DefaultObjectiveContext( (objective) -> log.debug("Termination l2 tunnel rule for {} populated", l2Tunnel.tunnelId()), (objective, error) -> log.warn("Failed to populate termination l2 tunnel rule for {}: {}", l2Tunnel.tunnelId(), error)); NextObjective nextObjective = nextObjectiveBuilder.add(context); srManager.flowObjectiveService.next(egress.deviceId(), nextObjective); log.debug("Termination next objective for {} not found. Creating new NextObj with id={}", l2Tunnel.tunnelId(), nextObjective.id() ); // We create the flow relative to the termination. ForwardingObjective.Builder fwdBuilder = createTermFwdObjective( l2Tunnel.pwLabel(), l2Tunnel.tunnelId(), egress.port(), nextObjective.id() ); context = new DefaultObjectiveContext( (objective) -> log.debug("FwdObj for tunnel termination {} populated", l2Tunnel.tunnelId()), (objective, error) -> log.warn("Failed to populate fwdrObj for tunnel termination {}", l2Tunnel.tunnelId(), error)); srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.add(context)); log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}", nextId, l2Tunnel.tunnelId()); return SUCCESS; } /** * Helper method to verify if the tunnel is whether or not * supported. * * @param l2Tunnel the tunnel to verify * @return the result of the verification */ private Result verifyTunnel(DefaultL2Tunnel l2Tunnel) { // Service delimiting tag not supported yet. if (!l2Tunnel.sdTag().equals(VlanId.NONE)) { log.warn("Service delimiting tag not supported yet"); return UNSUPPORTED; } // Tag mode not supported yet. if (l2Tunnel.pwMode() == TAGGED) { log.warn("Tagged mode not supported yet"); return UNSUPPORTED; } // Raw mode without service delimiting tag // is the only mode supported for now. return SUCCESS; } /** * Creates the filtering objective according to a given policy. * * @param inPort the in port * @param innerTag the inner vlan tag * @param outerTag the outer vlan tag * @return the filtering objective */ private FilteringObjective.Builder createFiltObjective(PortNumber inPort, VlanId innerTag, VlanId outerTag) { return DefaultFilteringObjective.builder() .withKey(Criteria.matchInPort(inPort)) .addCondition(Criteria.matchInnerVlanId(innerTag)) .addCondition(Criteria.matchVlanId(outerTag)) .withPriority(SegmentRoutingService.DEFAULT_PRIORITY) .permit() .fromApp(srManager.appId()); } /** * Creates the forwarding objective for the termination. * * @param pwLabel the pseudo wire label * @param tunnelId the tunnel id * @param egressPort the egress port * @param nextId the next step * @return the forwarding objective to support the termination */ private ForwardingObjective.Builder createTermFwdObjective(MplsLabel pwLabel, long tunnelId, PortNumber egressPort, int nextId) { TrafficSelector.Builder trafficSelector = DefaultTrafficSelector .builder(); TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment .builder(); // The flow has to match on the pw label and bos trafficSelector.matchEthType(Ethernet.MPLS_UNICAST); trafficSelector.matchMplsLabel(pwLabel); trafficSelector.matchMplsBos(true); // The flow has to decrement ttl, restore ttl in // pop mpls, set tunnel id and port. trafficTreatment.decMplsTtl(); trafficTreatment.copyTtlIn(); trafficTreatment.popMpls(); trafficTreatment.setTunnelId(tunnelId); trafficTreatment.setOutput(egressPort); return DefaultForwardingObjective.builder() .fromApp(srManager.appId()) .makePermanent() .nextStep(nextId) .withPriority(SegmentRoutingService.DEFAULT_PRIORITY) .withSelector(trafficSelector.build()) .withTreatment(trafficTreatment.build()) .withFlag(VERSATILE); } /** * Creates the forwarding objective for the initiation. * * @param tunnelId the tunnel id * @param inPort the input port * @param nextId the next step * @return the forwarding objective to support the initiation. */ private ForwardingObjective.Builder createInitFwdObjective(long tunnelId, PortNumber inPort, int nextId) { TrafficSelector.Builder trafficSelector = DefaultTrafficSelector .builder(); // The flow has to match on the mpls logical // port and the tunnel id. trafficSelector.matchTunnelId(tunnelId); trafficSelector.matchInPort(inPort); return DefaultForwardingObjective.builder() .fromApp(srManager.appId()) .makePermanent() .nextStep(nextId) .withPriority(SegmentRoutingService.DEFAULT_PRIORITY) .withSelector(trafficSelector.build()) .withFlag(VERSATILE); } /** * Creates the next objective according to a given * pipeline. We don't set the next id and we don't * create the final meta to check if we are re-using * the same next objective for different tunnels. * * @param pipeline the pipeline to support * @param srcCp the source port * @param dstCp the destination port * @param l2Tunnel the tunnel to support * @param egressId the egress device id * @return the next objective to support the pipeline */ private NextObjective.Builder createNextObjective(Pipeline pipeline, ConnectPoint srcCp, ConnectPoint dstCp, DefaultL2Tunnel l2Tunnel, DeviceId egressId) { NextObjective.Builder nextObjBuilder; TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); if (pipeline == INITIATION) { nextObjBuilder = DefaultNextObjective .builder() .withType(NextObjective.Type.SIMPLE) .fromApp(srManager.appId()); // The pw label is the bottom of stack. It has to // be different -1. if (l2Tunnel.pwLabel().toInt() == MplsLabel.MAX_MPLS) { log.warn("Pw label not configured"); return null; } treatmentBuilder.pushMpls(); treatmentBuilder.setMpls(l2Tunnel.pwLabel()); treatmentBuilder.setMplsBos(true); treatmentBuilder.copyTtlOut(); // If the inter-co label is present we have to set the label. if (l2Tunnel.interCoLabel().toInt() != MplsLabel.MAX_MPLS) { treatmentBuilder.pushMpls(); treatmentBuilder.setMpls(l2Tunnel.interCoLabel()); treatmentBuilder.setMplsBos(false); treatmentBuilder.copyTtlOut(); } // We retrieve the sr label from the config // using the egress leaf device id. MplsLabel srLabel; try { srLabel = MplsLabel.mplsLabel( srManager.deviceConfiguration().getIPv4SegmentId(egressId) ); } catch (DeviceConfigNotFoundException e) { log.warn("Sr label not configured"); return null; } treatmentBuilder.pushMpls(); treatmentBuilder.setMpls(srLabel); treatmentBuilder.setMplsBos(false); treatmentBuilder.copyTtlOut(); // We have to rewrite the src and dst mac address. MacAddress ingressMac; try { ingressMac = srManager .deviceConfiguration() .getDeviceMac(srcCp.deviceId()); } catch (DeviceConfigNotFoundException e) { log.warn("Was not able to find the ingress mac"); return null; } treatmentBuilder.setEthSrc(ingressMac); MacAddress neighborMac; try { neighborMac = srManager .deviceConfiguration() .getDeviceMac(dstCp.deviceId()); } catch (DeviceConfigNotFoundException e) { log.warn("Was not able to find the neighbor mac"); return null; } treatmentBuilder.setEthDst(neighborMac); } else { // We create the next objective which // will be a simple l2 group. nextObjBuilder = DefaultNextObjective .builder() .withType(NextObjective.Type.SIMPLE) .fromApp(srManager.appId()); } treatmentBuilder.setOutput(srcCp.port()); nextObjBuilder.addTreatment(treatmentBuilder.build()); return nextObjBuilder; } /** * Returns the next hop. * * @param srcCp the ingress connect point * @param dstCp the egress connect point * @return the next hop */ private Link getNextHop(ConnectPoint srcCp, ConnectPoint dstCp) { // We retrieve a set of disjoint paths. Set<DisjointPath> paths = srManager.pathService.getDisjointPaths( srcCp.elementId(), dstCp.elementId() ); // We randmly pick a path. if (paths.isEmpty()) { return null; } int size = paths.size(); int index = RandomUtils.nextInt(0, size); // We verify if the path is ok and there is not // a misconfiguration. List<Link> links = Iterables.get(paths, index).links(); checkState(links.size() == 2, WRONG_TOPOLOGY, links); return links.get(0); } /** * Deletes a given policy using the parameter supplied. * * @param tunnelId the tunnel id * @param ingress the ingress point * @param ingressInner the ingress inner vlan id * @param ingressOuter the ingress outer vlan id * @param future to perform the async operation * @param direction the direction: forward or reverse */ private void deletePolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner, VlanId ingressOuter, CompletableFuture<ObjectiveError> future, Direction direction) { if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) { log.info("Abort delete of policy for tunnel {}: I am not the master", tunnelId); if (future != null) { future.complete(null); } return; } String key = generateKey(tunnelId, direction); if (!l2InitiationNextObjStore.containsKey(key)) { log.warn("Abort delete of policy for tunnel {}: next does not exist in the store", tunnelId); if (future != null) { future.complete(null); } return; } NextObjective nextObjective = l2InitiationNextObjStore.get(key).value(); int nextId = nextObjective.id(); List<Objective> objectives = Lists.newArrayList(); // We create the forwarding objective. ForwardingObjective.Builder fwdBuilder = createInitFwdObjective( tunnelId, ingress.port(), nextId ); ObjectiveContext context = new ObjectiveContext() { @Override public void onSuccess(Objective objective) { log.debug("Previous fwdObj for policy {} removed", tunnelId); if (future != null) { future.complete(null); } } @Override public void onError(Objective objective, ObjectiveError error) { log.warn("Failed to remove previous fwdObj for policy {}: {}", tunnelId, error); if (future != null) { future.complete(error); } } }; objectives.add(fwdBuilder.remove(context)); // We create the filtering objective to define the // permit traffic in the switch FilteringObjective.Builder filtBuilder = createFiltObjective( ingress.port(), ingressInner, ingressOuter ); TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder() .setTunnelId(tunnelId); filtBuilder.withMeta(treatment.build()); context = new DefaultObjectiveContext( (objective) -> log.debug("FilterObj for policy {} revoked", tunnelId), (objective, error) -> log.warn("Failed to revoke filterObj for policy {}", tunnelId, error)); objectives.add(filtBuilder.remove(context)); for (Objective objective : objectives) { if (objective instanceof ForwardingObjective) { srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective); } else { srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective); } } } /** * Deletes the pseudo wire initiation. * * @param l2TunnelId the tunnel id * @param ingress the ingress connect point * @param future to perform an async operation * @param direction the direction: reverse of forward */ private void tearDownPseudoWireInit(long l2TunnelId, ConnectPoint ingress, CompletableFuture<ObjectiveError> future, Direction direction) { String key = generateKey(l2TunnelId, direction); if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) { log.info("Abort delete of {} for {}: I am not the master", INITIATION, key); if (future != null) { future.complete(null); } return; } if (!l2InitiationNextObjStore.containsKey(key)) { log.info("Abort delete of {} for {}: next does not exist in the store", INITIATION, key); if (future != null) { future.complete(null); } return; } NextObjective nextObjective = l2InitiationNextObjStore.get(key).value(); ObjectiveContext context = new ObjectiveContext() { @Override public void onSuccess(Objective objective) { log.debug("Previous {} next for {} removed", INITIATION, key); if (future != null) { future.complete(null); } } @Override public void onError(Objective objective, ObjectiveError error) { log.warn("Failed to remove previous {} next for {}: {}", INITIATION, key, error); if (future != null) { future.complete(error); } } }; srManager.flowObjectiveService .next(ingress.deviceId(), (NextObjective) nextObjective.copy().remove(context)); l2InitiationNextObjStore.remove(key); } /** * Deletes the pseudo wire termination. * * @param l2Tunnel the tunnel * @param egress the egress connect point * @param future the async task * @param direction the direction of the tunnel */ private void tearDownPseudoWireTerm(DefaultL2Tunnel l2Tunnel, ConnectPoint egress, CompletableFuture<ObjectiveError> future, Direction direction) { /* * We verify the mastership for the termination. */ String key = generateKey(l2Tunnel.tunnelId(), direction); if (!srManager.mastershipService.isLocalMaster(egress.deviceId())) { log.info("Abort delete of {} for {}: I am not the master", TERMINATION, key); if (future != null) { future.complete(null); } return; } if (!l2TerminationNextObjStore.containsKey(key)) { log.info("Abort delete of {} for {}: next does not exist in the store", TERMINATION, key); if (future != null) { future.complete(null); } return; } NextObjective nextObjective = l2TerminationNextObjStore.get(key).value(); ForwardingObjective.Builder fwdBuilder = createTermFwdObjective( l2Tunnel.pwLabel(), l2Tunnel.tunnelId(), egress.port(), nextObjective.id() ); ObjectiveContext context = new DefaultObjectiveContext( (objective) -> log.debug("FwdObj for {} {} removed", TERMINATION, l2Tunnel.tunnelId()), (objective, error) -> log.warn("Failed to remove fwdObj for {} {}", TERMINATION, l2Tunnel.tunnelId(), error)); srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.remove(context)); context = new ObjectiveContext() { @Override public void onSuccess(Objective objective) { log.debug("Previous {} next for {} removed", TERMINATION, key); if (future != null) { future.complete(null); } } @Override public void onError(Objective objective, ObjectiveError error) { log.warn("Failed to remove previous {} next for {}: {}", TERMINATION, key, error); if (future != null) { future.complete(error); } } }; srManager.flowObjectiveService .next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context)); l2TerminationNextObjStore.remove(key); } /** * Utilities to generate pw key. * * @param tunnelId the tunnel id * @param direction the direction of the pw * @return the key of the store */ private String generateKey(long tunnelId, Direction direction) { return String.format("%s-%s", tunnelId, direction); } /** * Pwaas pipelines. */ protected enum Pipeline { /** * The initiation pipeline. */ INITIATION, /** * The termination pipeline. */ TERMINATION } /** * Enum helper to carry the outcomes of an operation. */ public enum Result { /** * Happy ending scenario it has been created. */ SUCCESS(0, "It has been Created"), /** * We have problems with the supplied parameters. */ WRONG_PARAMETERS(1, "Wrong parameters"), /** * It already exists. */ ID_EXISTS(2, "The id already exists"), /** * We have an internal error during the deployment * phase. */ INTERNAL_ERROR(3, "Internal error"), /** * The operation is not supported. */ UNSUPPORTED(4, "Unsupported"); private final int code; private final String description; private int nextId; Result(int code, String description) { this.code = code; this.description = description; } public String getDescription() { return description; } @Override public String toString() { return code + ": " + description; } } /** * Enum helper for handling the direction of the pw. */ public enum Direction { /** * The forward direction of the pseudo wire. */ FWD, /** * The reverse direction of the pseudo wire. */ REV } }