blob: f01d4077bc1c409a4b87383af6755cd291911a20 [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.pi.demo.app.tor;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Path;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.group.GroupService;
import org.onosproject.net.pi.runtime.PiAction;
import org.onosproject.net.pi.runtime.PiActionGroupId;
import org.onosproject.net.pi.runtime.PiActionParam;
import org.onosproject.net.pi.runtime.PiGroupKey;
import org.onosproject.net.pi.runtime.PiTableAction;
import org.onosproject.net.topology.DefaultTopologyVertex;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyGraph;
import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.collect.Collections2.permutations;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.toSet;
import static org.onlab.util.ImmutableByteSequence.copyFrom;
import static org.onosproject.pi.demo.app.tor.Combination.combinations;
import static org.onosproject.pi.demo.app.tor.TorInterpreter.*;
/**
* Implementation of an upgradable fabric app for TOR configuration.
*/
@Component(immediate = true)
public class TorApp extends AbstractUpgradableFabricApp {
private static final String APP_NAME = "org.onosproject.pi-tor";
// DeviceId -> Group reference (ports, smac, dmac) -> group id
private static final Map<DeviceId, Map<Triple<Set<PortNumber>, MacAddress, MacAddress>, Integer>>
DEVICE_GROUP_ID_MAP = Maps.newHashMap();
private static final boolean USE_ACTION_WITH_SINGLE_MEMBER_GROUP = false;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private GroupService groupService;
private final Set<Pair<DeviceId, GroupKey>> groupKeys = Sets.newHashSet();
public TorApp() {
super(APP_NAME, TorPipeconfFactory.getAll());
}
@Deactivate
public void deactivate() {
groupKeys.forEach(pair -> groupService.removeGroup(pair.getLeft(), pair.getRight(), appId));
super.deactivate();
}
@Override
public boolean initDevice(DeviceId deviceId) {
// Nothing to do.
return true;
}
public List<FlowRule> generateTestFlowRules(DeviceId deviceId) throws FlowRuleGeneratorException {
/*
To test if the flow rules and groups are working, host pinging should work with this mn configuration:
$ sudo -E mn --custom $BMV2_MN_PY \
--switch onosbmv2,pipeconfId=bmv2-tor \
--controller remote,ip=192.168.56.1 --mac
*/
List<FlowRule> rules = Lists.newArrayList();
MacAddress ethAddr1 = MacAddress.valueOf("00:00:00:00:00:01");
MacAddress ethAddr2 = MacAddress.valueOf("00:00:00:00:00:02");
IpAddress ipAddr1 = Ip4Address.valueOf("10.0.0.1");
IpAddress ipAddr2 = Ip4Address.valueOf("10.0.0.2");
PortNumber outPort1 = PortNumber.portNumber(1);
PortNumber outPort2 = PortNumber.portNumber(2);
// Filtering (match on eth, NoAction)
rules.addAll(generateFilteringRules(deviceId, Sets.newHashSet(ethAddr1, ethAddr2)));
try {
PiTableAction groupAction1 = provisionGroup(deviceId, singleton(outPort1), ethAddr2, ethAddr1);
PiTableAction groupAction2 = provisionGroup(deviceId, singleton(outPort2), ethAddr1, ethAddr2);
// Forwarding
rules.add(
flowRuleBuilder(deviceId, L3_FWD_TBL_ID)
.withSelector(
DefaultTrafficSelector.builder()
.matchIPDst(ipAddr1.toIpPrefix())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.piTableAction(groupAction1)
.build())
.build());
rules.add(
flowRuleBuilder(deviceId, L3_FWD_TBL_ID)
.withSelector(
DefaultTrafficSelector.builder()
.matchIPDst(ipAddr2.toIpPrefix())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.piTableAction(groupAction2)
.build())
.build());
} catch (Throwable e) {
log.error("{}", e);
}
return rules;
}
@Override
public List<FlowRule> generateLeafRules(DeviceId leaf, Host localHost, Set<Host> remoteHosts,
Collection<DeviceId> availableSpines, Topology topo)
throws FlowRuleGeneratorException {
// Get ports which connect this leaf switch to hosts.
Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
.stream()
.filter(port -> !isFabricPort(port, topo))
.map(Port::number)
.collect(toSet());
// Get ports which connect this leaf to the given available spines.
TopologyGraph graph = topologyService.getGraph(topo);
Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
.stream()
.filter(e -> availableSpines.contains(e.dst().deviceId()))
.map(e -> e.link().src().port())
.collect(toSet());
if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
hostPorts.size(), fabricPorts.size());
throw new FlowRuleGeneratorException();
}
PortNumber hostPort = hostPorts.iterator().next();
List<FlowRule> rules = Lists.newArrayList();
// Filtering rules
Set<MacAddress> remoteMacAddrs = remoteHosts.stream().map(Host::mac).collect(toSet());
rules.addAll(generateFilteringRules(leaf, remoteMacAddrs));
rules.addAll(generateFilteringRules(leaf, singleton(localHost.mac())));
// From local host to remote ones.
for (Host remoteHost : remoteHosts) {
PiTableAction piTableAction = provisionGroup(leaf, fabricPorts, localHost.mac(), remoteHost.mac());
for (IpAddress ipAddr : remoteHost.ipAddresses()) {
FlowRule rule = flowRuleBuilder(leaf, L3_FWD_TBL_ID)
.withSelector(
DefaultTrafficSelector.builder()
.matchIPDst(ipAddr.toIpPrefix())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.piTableAction(piTableAction)
.build())
.build();
rules.add(rule);
}
}
// From remote hosts to the local one
for (IpAddress dstIpAddr : localHost.ipAddresses()) {
for (Host remoteHost : remoteHosts) {
PiTableAction tableAction = provisionGroup(leaf, singleton(hostPort), remoteHost.mac(),
localHost.mac());
FlowRule rule = flowRuleBuilder(leaf, L3_FWD_TBL_ID)
.withSelector(
DefaultTrafficSelector.builder()
.matchIPDst(dstIpAddr.toIpPrefix())
.build())
.withTreatment(
DefaultTrafficTreatment.builder()
.piTableAction(tableAction)
.build())
.build();
rules.add(rule);
}
}
return rules;
}
@Override
public List<FlowRule> generateSpineRules(DeviceId spine, Set<Host> hosts, Topology topo)
throws FlowRuleGeneratorException {
List<FlowRule> rules = Lists.newArrayList();
Set<MacAddress> macAddrs = hosts.stream().map(Host::mac).collect(toSet());
rules.addAll(generateFilteringRules(spine, macAddrs));
// For each host pair (src -> dst)
for (Set<Host> hostCombs : combinations(hosts, 2)) {
for (List<Host> hostPair : permutations(hostCombs)) {
Host srcHost = hostPair.get(0);
Host dstHost = hostPair.get(1);
Set<Path> paths = topologyService.getPaths(topo, spine, dstHost.location().deviceId());
if (paths.size() == 0) {
log.warn("Can't find any path between spine {} and host {}", spine, dstHost);
throw new FlowRuleGeneratorException();
}
Set<PortNumber> ports = paths.stream().map(p -> p.src().port()).collect(toSet());
PiTableAction piTableAction = provisionGroup(spine, ports, srcHost.mac(), dstHost.mac());
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(piTableAction)
.build();
for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
FlowRule rule = flowRuleBuilder(spine, L3_FWD_TBL_ID)
.withSelector(
DefaultTrafficSelector.builder()
.matchIPDst(dstIpAddr.toIpPrefix())
.build())
.withTreatment(treatment)
.build();
rules.add(rule);
}
}
}
return rules;
}
private List<FlowRule> generateFilteringRules(DeviceId deviceId, Collection<MacAddress> macAddrs)
throws FlowRuleGeneratorException {
List<FlowRule> rules = Lists.newArrayList();
for (MacAddress macAddr : macAddrs) {
FlowRule rule = flowRuleBuilder(deviceId, L3_FILTER_TBL_ID)
.withSelector(DefaultTrafficSelector.builder()
.matchEthDst(macAddr)
.build())
.withTreatment(DefaultTrafficTreatment.builder()
.piTableAction(PiAction.builder()
.withId(NO_ACTION_ID)
.build())
.build())
.build();
rules.add(rule);
}
return rules;
}
private PiTableAction provisionGroup(DeviceId deviceId, Set<PortNumber> ports, MacAddress smac,
MacAddress dmac) {
if (USE_ACTION_WITH_SINGLE_MEMBER_GROUP && ports.size() == 1) {
return nextHopAction(ports.iterator().next(), smac, dmac);
}
DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
Triple<Set<PortNumber>, MacAddress, MacAddress> groupRef = ImmutableTriple.of(ports, smac, dmac);
if (!DEVICE_GROUP_ID_MAP.get(deviceId).containsKey(groupRef)) {
// Provision group to device
// We get a group ID by counting the number of unique <ports, smac, dmac> triples for each deviceId.
// Each distinct triple will have a unique group ID unique in the scope of a device.
int groupId = DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1;
// Group buckets
List<GroupBucket> bucketList = ports.stream()
.map(port -> nextHopAction(port, smac, dmac))
.map(piAction -> DefaultTrafficTreatment.builder().piTableAction(piAction).build())
.map(DefaultGroupBucket::createSelectGroupBucket)
.collect(Collectors.toList());
// Group cookie (with action profile ID)
PiGroupKey groupKey = new PiGroupKey(L3_FWD_TBL_ID, WCMP_ACT_PROF_ID, groupId);
groupKeys.add(ImmutablePair.of(deviceId, groupKey));
// Group desc
GroupDescription groupDesc = new DefaultGroupDescription(deviceId,
GroupDescription.Type.SELECT,
new GroupBuckets(bucketList),
groupKey,
groupId,
appId);
log.info("Adding group {} to {}...", groupId, deviceId);
groupService.addGroup(groupDesc);
DEVICE_GROUP_ID_MAP.get(deviceId).put(groupRef, groupId);
}
return PiActionGroupId.of(DEVICE_GROUP_ID_MAP.get(deviceId).get(groupRef));
}
private PiAction nextHopAction(PortNumber port, MacAddress smac, MacAddress dmac) {
return PiAction.builder()
.withId(SET_NEXT_HOP_ACT_ID)
.withParameter(new PiActionParam(PORT_ACT_PRM_ID, copyFrom(port.toLong())))
// Ignore L3 routing behaviour by keeping the original host mac addresses at each hop.
.withParameter(new PiActionParam(SMAC_ACT_PRM_ID, copyFrom(smac.toBytes())))
.withParameter(new PiActionParam(DMAC_ACT_PRM_ID, copyFrom(dmac.toBytes())))
.build();
}
}