blob: 5f0ed351bea7dffe9b9ccf0359acda3852530d0c [file] [log] [blame]
/*
* 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.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.Result.*;
/**
* Handles pwaas related events.
*/
public class L2TunnelHandler {
private static final Logger log = LoggerFactory.getLogger(L2TunnelHandler.class);
private static final String FWD = "f";
private static final String REV = "r";
private static final String NOT_MASTER = "Not master controller";
private static final String WRONG_TOPOLOGY = "Path in leaf-spine topology" +
" should always be two hops: ";
private final SegmentRoutingManager srManager;
private final ConsistentMap<String, NextObjective> l2InitiationNextObjStore;
/**
* TODO a proper store is necessary to handle the policies and collisions.
*/
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();
}
/**
* 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);
}
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 cannot be 0");
continue;
}
// We do a sanity check of the pseudo wire.
result = verifyPseudoWire(currentL2Tunnel);
if (result != SUCCESS) {
continue;
}
// We establish the tunnel.
result = deployPseudoWire(
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 establish the reverse tunnel.
result = deployPseudoWire(
currentL2Tunnel.l2Tunnel(),
currentL2Tunnel.l2TunnelPolicy().cP2(),
currentL2Tunnel.l2TunnelPolicy().cP1(),
REV
);
if (result != SUCCESS) {
continue;
}
deployPolicy(
l2TunnelId,
currentL2Tunnel.l2TunnelPolicy().cP2(),
currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(),
currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
result.nextId
);
}
}
/**
* 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();
String fwdKey = generateKey(tunnelId, FWD);
String revKey = generateKey(tunnelId, REV);
Result result;
NextObjective fwdNextObjective;
NextObjective revNextObjective;
// The async tasks to orchestrate the next and
// forwarding update.
CompletableFuture<ObjectiveError> revPolicyFuture = new CompletableFuture<>();
CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
CompletableFuture<ObjectiveError> newPwFuture = new CompletableFuture<>();
result = verifyPseudoWire(newPw);
if (result != SUCCESS) {
return;
}
if (!l2InitiationNextObjStore.containsKey(fwdKey)) {
log.warn("NextObj for {} does not exist in the store.", fwdKey);
return;
}
fwdNextObjective = l2InitiationNextObjStore.get(fwdKey).value();
if (!l2InitiationNextObjStore.containsKey(revKey)) {
log.warn("NextObj for {} does not exist in the store.", revKey);
return;
}
// First we remove both policy.
revNextObjective = l2InitiationNextObjStore.get(revKey).value();
log.debug("Start deleting fwd policy for {}", tunnelId);
deletePolicy(
tunnelId,
oldPw.l2TunnelPolicy().cP1(),
oldPw.l2TunnelPolicy().cP1InnerTag(),
oldPw.l2TunnelPolicy().cP1OuterTag(),
fwdNextObjective.id(),
revPolicyFuture
);
revPolicyFuture.thenAcceptAsync(status -> {
if (status == null) {
log.debug("Fwd policy removed. Now remove rev policy for {}", tunnelId);
deletePolicy(
tunnelId,
oldPw.l2TunnelPolicy().cP2(),
oldPw.l2TunnelPolicy().cP2InnerTag(),
oldPw.l2TunnelPolicy().cP2OuterTag(),
revNextObjective.id(),
fwdInitNextFuture
);
}
});
// Finally we remove both the tunnels.
fwdInitNextFuture.thenAcceptAsync(status -> {
if (status == null) {
log.debug("Rev policy removed. Now remove fwd pw for {}", tunnelId);
tearDownPseudoWire(
fwdKey,
fwdNextObjective,
oldPw.l2TunnelPolicy().cP1(),
oldPw.l2TunnelPolicy().cP2(),
revInitNextFuture
);
}
});
revInitNextFuture.thenAcceptAsync(status -> {
if (status == null) {
log.debug("Fwd tunnel removed. Now remove rev pw for {}", tunnelId);
tearDownPseudoWire(
revKey,
revNextObjective,
oldPw.l2TunnelPolicy().cP2(),
oldPw.l2TunnelPolicy().cP1(),
newPwFuture
);
}
});
// At the end we install the new pw.
newPwFuture.thenAcceptAsync(status -> {
if (status == null) {
log.debug("Deploying new fwd pw for {}", tunnelId);
Result lamdaResult = deployPseudoWire(
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
);
log.debug("Deploying new rev pw for {}", tunnelId);
lamdaResult = deployPseudoWire(
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
);
}
});
}
/**
* 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;
int nextId;
NextObjective nextObjective;
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;
}
String key = generateKey(l2TunnelId, FWD);
if (!l2InitiationNextObjStore.containsKey(key)) {
log.warn("NextObj for {} does not exist in the store.", key);
continue;
}
nextObjective = l2InitiationNextObjStore.get(key).value();
nextId = nextObjective.id();
// First all we have to delete the policy.
deletePolicy(
l2TunnelId,
currentL2Tunnel.l2TunnelPolicy().cP1(),
currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(),
currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(),
nextId,
null
);
// Finally we will tear down the pseudo wire.
tearDownPseudoWire(
key,
nextObjective,
currentL2Tunnel.l2TunnelPolicy().cP1(),
currentL2Tunnel.l2TunnelPolicy().cP2(),
null
);
// We do the same operations on the reverse side.
key = generateKey(l2TunnelId, REV);
if (!l2InitiationNextObjStore.containsKey(key)) {
log.warn("NextObj for {} does not exist in the store.", key);
continue;
}
nextObjective = l2InitiationNextObjStore.get(key).value();
nextId = nextObjective.id();
deletePolicy(
l2TunnelId,
currentL2Tunnel.l2TunnelPolicy().cP2(),
currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(),
currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
nextId,
null
);
tearDownPseudoWire(
key,
nextObjective,
currentL2Tunnel.l2TunnelPolicy().cP2(),
currentL2Tunnel.l2TunnelPolicy().cP1(),
null
);
}
}
/**
* 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;
}
/**
* TODO Operation on the policies store.
*
* 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 SUCCESS if the policy has been deployed.
* Otherwise an error according to the failure
* scenario.
*/
private Result deployPolicy(long tunnelId,
ConnectPoint ingress,
VlanId ingressInner,
VlanId ingressOuter,
int nextId) {
ForwardingObjective.Builder fwdBuilder;
FilteringObjective.Builder filtBuilder;
List<Objective> objectives = Lists.newArrayList();
if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
log.info("Abort creation of policy for L2 tunnel {}: {}", tunnelId, NOT_MASTER);
return SUCCESS;
}
// We create the forwarding objective for supporting
// the l2 tunnel.
fwdBuilder = createFwdObjective(
INITIATION,
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
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 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;
}
/**
* TODO Operation on the policies store.
*
* Handles the tunnel establishment which consists in
* create the next objectives related to the initiation
* and termination.
*
* @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 SUCCESS if the tunnel has been created.
* Otherwise an error according to the failure
* scenario
*/
private Result deployPseudoWire(DefaultL2Tunnel l2Tunnel,
ConnectPoint ingress,
ConnectPoint egress,
String direction) {
Link nextHop;
NextObjective.Builder nextObjectiveBuilder;
NextObjective nextObjective;
int nextId;
Result result;
if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
log.info("Abort initiation creation of L2 tunnel {}: {}",
l2Tunnel.tunnelId(), NOT_MASTER);
return SUCCESS;
}
// We need at least a path between ingress and egress.
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 ?
nextObjectiveBuilder = createNextObjective(
INITIATION,
nextHop,
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);
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 = 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 = SUCCESS;
result.nextId = nextObjective.id();
return result;
}
/**
* 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;
}
/**
* Create 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);
}
/**
* Create the forwarding objective according to a given pipeline.
*
* @param pipeline the pipeline
* @param tunnelId the tunnel id
* @param nextId the next step
* @return the forwarding objective to support the pipeline.
*/
private ForwardingObjective.Builder createFwdObjective(Pipeline pipeline,
long tunnelId,
PortNumber inPort,
int nextId) {
ForwardingObjective.Builder fwdBuilder = null;
TrafficSelector.Builder trafficSelector = DefaultTrafficSelector
.builder();
if (pipeline == INITIATION) {
// The flow has to match on the mpls logical
// port and the tunnel id.
trafficSelector.matchTunnelId(tunnelId);
trafficSelector.matchInPort(inPort);
fwdBuilder = DefaultForwardingObjective.builder()
.fromApp(srManager.appId)
.makePermanent()
.nextStep(nextId)
.withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
.withSelector(trafficSelector.build())
.withFlag(VERSATILE);
}
return fwdBuilder;
}
/**
* 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 nextHop the next hop towards the destination
* @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,
Link nextHop,
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(nextHop.src().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(nextHop.dst().deviceId());
} catch (DeviceConfigNotFoundException e) {
log.warn("Was not able to find the neighbor mac");
return null;
}
treatmentBuilder.setEthDst(neighborMac);
} else {
nextObjBuilder = DefaultNextObjective
.builder()
.withType(NextObjective.Type.SIMPLE)
.fromApp(srManager.appId);
}
treatmentBuilder.setOutput(nextHop.src().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);
}
/**
* TODO Operation on the store.
* 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 nextId the next objective id
*/
private void deletePolicy(long tunnelId,
ConnectPoint ingress,
VlanId ingressInner,
VlanId ingressOuter,
int nextId,
CompletableFuture<ObjectiveError> fwdFuture) {
ForwardingObjective.Builder fwdBuilder;
FilteringObjective.Builder filtBuilder;
List<Objective> objectives = Lists.newArrayList();
if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
log.info("Abort delete of policy for L2 tunnel {}: {}", tunnelId, NOT_MASTER);
return;
}
// We create the forwarding objective.
fwdBuilder = createFwdObjective(
INITIATION,
tunnelId,
ingress.port(),
nextId
);
ObjectiveContext context = new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
log.debug("Previous FwdObj for policy {} removed", tunnelId);
if (fwdFuture != null) {
fwdFuture.complete(null);
}
}
@Override
public void onError(Objective objective, ObjectiveError error) {
log.warn("Failed to remove previous FwdObj for policy {}: {}", tunnelId, error);
if (fwdFuture != null) {
fwdFuture.complete(error);
}
}
};
objectives.add(fwdBuilder.remove(context));
// We create the filtering objective to define the
// permit traffic in the switch
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);
}
}
}
/**
* TODO Operation on the store.
* Deletes a given pseudo wire using the parameter supplied.
*
* @param key the key of the store
* @param nextObjective the next objective representing the pw
* @param ingress the ingress connect point
* @param egress the egress connect point
*/
private void tearDownPseudoWire(String key,
NextObjective nextObjective,
ConnectPoint ingress,
ConnectPoint egress,
CompletableFuture<ObjectiveError> nextFutureForInit) {
if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
log.info("Abort delete of {} for {}: {}", INITIATION, key, NOT_MASTER);
return;
}
ObjectiveContext context = new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
log.debug("Previous {} NextObj for {} removed", INITIATION, key);
if (nextFutureForInit != null) {
nextFutureForInit.complete(null);
}
}
@Override
public void onError(Objective objective, ObjectiveError error) {
log.warn("Failed to remove previous {} NextObj for {}: {}", INITIATION, key, error);
if (nextFutureForInit != null) {
nextFutureForInit.complete(error);
}
}
};
srManager.flowObjectiveService.next(
ingress.deviceId(),
(NextObjective) nextObjective.copy().remove(context)
);
l2InitiationNextObjStore.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, String direction) {
return String.format("%s-%s", tunnelId, direction);
}
/**
* VPWS 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;
private Result(int code, String description) {
this.code = code;
this.description = description;
}
public String getDescription() {
return description;
}
public int getCode() {
return code;
}
@Override
public String toString() {
return code + ": " + description;
}
}
}