| /* |
| * Copyright 2016-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.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 |
| } |
| |
| } |