blob: c8ecdd7b1947de48e53d77883009074ca17adef3 [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.simplefabric;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.EncapsulationType;
import org.onosproject.net.FilteredConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.ResourceGroup;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.Constraint;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.intent.SinglePointToMultiPointIntent;
import org.onosproject.net.intent.constraint.EncapsulationConstraint;
import org.onosproject.net.intent.constraint.PartialFailureConstraint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.stream.Collectors;
/**
* An implementation of L2NetworkOperationService.
* Handles the execution order of the L2 Network operations generated by the
* application.
*/
@Component(immediate = true, enabled = false)
public class SimpleFabricL2Forward {
public static final String BROADCAST = "BCAST";
public static final String UNICAST = "UNI";
private final Logger log = LoggerFactory.getLogger(getClass());
protected ApplicationId l2ForwardAppId;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentService intentService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected SimpleFabricService simpleFabric;
public static final ImmutableList<Constraint> L2NETWORK_CONSTRAINTS =
ImmutableList.of(new PartialFailureConstraint());
private Map<Key, SinglePointToMultiPointIntent> bctIntentsMap = Maps.newConcurrentMap();
private Map<Key, MultiPointToSinglePointIntent> uniIntentsMap = Maps.newConcurrentMap();
private Set<Key> toBePurgedIntentKeys = new HashSet<>();
private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
@Activate
public void activate() {
l2ForwardAppId = coreService.registerApplication(simpleFabric.L2FORWARD_APP_ID);
log.info("simple fabric l2 forwaring starting with l2net app id {}", l2ForwardAppId.toString());
simpleFabric.addListener(simpleFabricListener);
refresh();
checkIntentsPurge();
log.info("simple fabric l2forward started");
}
@Deactivate
public void deactivate() {
log.info("simple fabric l2forward stopping");
simpleFabric.removeListener(simpleFabricListener);
for (Intent intent : bctIntentsMap.values()) {
intentService.withdraw(intent);
toBePurgedIntentKeys.add(intent.key());
}
for (Intent intent : uniIntentsMap.values()) {
intentService.withdraw(intent);
toBePurgedIntentKeys.add(intent.key());
}
for (Key key : toBePurgedIntentKeys) {
Intent intentToPurge = intentService.getIntent(key);
if (intentToPurge != null) {
intentService.purge(intentToPurge);
}
}
// do not set clear for switch compatibility
//bctIntentsMap.clear();
//uniIntentsMap.clear();
log.info("simple fabric l2forward stopped");
}
private void refresh() {
log.debug("simple fabric l2forward refresh");
Map<Key, SinglePointToMultiPointIntent> newBctIntentsMap = Maps.newConcurrentMap();
Map<Key, MultiPointToSinglePointIntent> newUniIntentsMap = Maps.newConcurrentMap();
for (L2Network l2Network : simpleFabric.getL2Networks()) {
// scans all l2network regardless of dirty flag
// if l2Network.l2Forward == false or number of interfaces() < 2, no Intents generated
for (SinglePointToMultiPointIntent intent : buildBrcIntents(l2Network)) {
newBctIntentsMap.put(intent.key(), intent);
}
for (MultiPointToSinglePointIntent intent : buildUniIntents(l2Network, hostsFromL2Network(l2Network))) {
newUniIntentsMap.put(intent.key(), intent);
}
if (l2Network.dirty()) {
l2Network.setDirty(false);
}
}
boolean bctUpdated = false;
for (SinglePointToMultiPointIntent intent : bctIntentsMap.values()) {
SinglePointToMultiPointIntent newIntent = newBctIntentsMap.get(intent.key());
if (newIntent == null) {
log.info("simple fabric l2forward withdraw broadcast intent: {}", intent.key().toString());
toBePurgedIntentKeys.add(intent.key());
intentService.withdraw(intent);
bctUpdated = true;
}
}
for (SinglePointToMultiPointIntent intent : newBctIntentsMap.values()) {
SinglePointToMultiPointIntent oldIntent = bctIntentsMap.get(intent.key());
if (oldIntent == null ||
!oldIntent.filteredEgressPoints().equals(intent.filteredEgressPoints()) ||
!oldIntent.filteredIngressPoint().equals(intent.filteredIngressPoint()) ||
!oldIntent.selector().equals(intent.selector()) ||
!oldIntent.treatment().equals(intent.treatment()) ||
!oldIntent.constraints().equals(intent.constraints())) {
log.info("simple fabric l2forward submit broadcast intent: {}", intent.key().toString());
toBePurgedIntentKeys.remove(intent.key());
intentService.submit(intent);
bctUpdated = true;
}
}
boolean uniUpdated = false;
for (MultiPointToSinglePointIntent intent : uniIntentsMap.values()) {
MultiPointToSinglePointIntent newIntent = newUniIntentsMap.get(intent.key());
if (newIntent == null) {
log.info("simple fabric l2forward withdraw unicast intent: {}", intent.key().toString());
toBePurgedIntentKeys.add(intent.key());
intentService.withdraw(intent);
uniUpdated = true;
}
}
for (MultiPointToSinglePointIntent intent : newUniIntentsMap.values()) {
MultiPointToSinglePointIntent oldIntent = uniIntentsMap.get(intent.key());
if (oldIntent == null ||
!oldIntent.filteredEgressPoint().equals(intent.filteredEgressPoint()) ||
!oldIntent.filteredIngressPoints().equals(intent.filteredIngressPoints()) ||
!oldIntent.selector().equals(intent.selector()) ||
!oldIntent.treatment().equals(intent.treatment()) ||
!oldIntent.constraints().equals(intent.constraints())) {
log.info("simple fabric l2forward submit unicast intent: {}", intent.key().toString());
toBePurgedIntentKeys.remove(intent.key());
intentService.submit(intent);
uniUpdated = true;
}
}
if (bctUpdated) {
bctIntentsMap = newBctIntentsMap;
}
if (uniUpdated) {
uniIntentsMap = newUniIntentsMap;
}
}
private void checkIntentsPurge() {
// check intents to be purge
if (!toBePurgedIntentKeys.isEmpty()) {
Set<Key> purgedKeys = new HashSet<>();
for (Key key : toBePurgedIntentKeys) {
Intent intentToPurge = intentService.getIntent(key);
if (intentToPurge == null) {
log.info("simple fabric l2forward purged intent: key={}", key.toString());
purgedKeys.add(key);
} else {
switch (intentService.getIntentState(key)) {
case FAILED:
case WITHDRAWN:
log.info("simple fabric l2forward try to purge intent: key={}", key.toString());
intentService.purge(intentToPurge);
break;
case INSTALL_REQ:
case INSTALLED:
case INSTALLING:
case RECOMPILING:
case COMPILING:
log.warn("simple fabric l2forward withdraw intent to purge: key={}", key);
intentService.withdraw(intentToPurge);
break;
case WITHDRAW_REQ:
case WITHDRAWING:
case PURGE_REQ:
case CORRUPT:
default:
// no action
break;
}
}
}
toBePurgedIntentKeys.removeAll(purgedKeys);
}
}
// Generates Unicast Intents and broadcast Intents for the L2 Network.
private Set<Intent> generateL2NetworkIntents(L2Network l2Network) {
return new ImmutableSet.Builder<Intent>()
.addAll(buildBrcIntents(l2Network))
.addAll(buildUniIntents(l2Network, hostsFromL2Network(l2Network)))
.build();
}
// Build Boadcast Intents for a L2 Network.
private Set<SinglePointToMultiPointIntent> buildBrcIntents(L2Network l2Network) {
Set<Interface> interfaces = l2Network.interfaces();
if (interfaces.size() < 2 || !l2Network.l2Forward() || !l2Network.l2Broadcast()) {
return ImmutableSet.of();
}
Set<SinglePointToMultiPointIntent> brcIntents = Sets.newHashSet();
ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
// Generates broadcast Intents from any network interface to other
// network interface from the L2 Network.
interfaces
.forEach(src -> {
FilteredConnectPoint srcFcp = buildFilteredConnectedPoint(src);
Set<FilteredConnectPoint> dstFcps = interfaces.stream()
.filter(iface -> !iface.equals(src))
.map(this::buildFilteredConnectedPoint)
.collect(Collectors.toSet());
Key key = buildKey(l2Network.name(), "BCAST", srcFcp.connectPoint(), MacAddress.BROADCAST);
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthDst(MacAddress.BROADCAST)
.build();
SinglePointToMultiPointIntent.Builder intentBuilder = SinglePointToMultiPointIntent.builder()
.appId(l2ForwardAppId)
.key(key)
.selector(selector)
.filteredIngressPoint(srcFcp)
.filteredEgressPoints(dstFcps)
.constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
.priority(SimpleFabricService.PRI_L2NETWORK_BROADCAST)
.resourceGroup(resourceGroup);
brcIntents.add(intentBuilder.build());
});
return brcIntents;
}
// Builds unicast Intents for a L2 Network.
private Set<MultiPointToSinglePointIntent> buildUniIntents(L2Network l2Network, Set<Host> hosts) {
Set<Interface> interfaces = l2Network.interfaces();
if (!l2Network.l2Forward() || interfaces.size() < 2) {
return ImmutableSet.of();
}
Set<MultiPointToSinglePointIntent> uniIntents = Sets.newHashSet();
ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
hosts.forEach(host -> {
FilteredConnectPoint hostFcp = buildFilteredConnectedPoint(host);
Set<FilteredConnectPoint> srcFcps = interfaces.stream()
.map(this::buildFilteredConnectedPoint)
.filter(fcp -> !fcp.equals(hostFcp))
.collect(Collectors.toSet());
Key key = buildKey(l2Network.name(), "UNI", hostFcp.connectPoint(), host.mac());
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthDst(host.mac()).build();
MultiPointToSinglePointIntent.Builder intentBuilder = MultiPointToSinglePointIntent.builder()
.appId(l2ForwardAppId)
.key(key)
.selector(selector)
.filteredIngressPoints(srcFcps)
.filteredEgressPoint(hostFcp)
.constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
.priority(SimpleFabricService.PRI_L2NETWORK_UNICAST)
.resourceGroup(resourceGroup);
uniIntents.add(intentBuilder.build());
});
return uniIntents;
}
// Intent generate utilities
private Set<Host> hostsFromL2Network(L2Network l2Network) {
Set<Interface> interfaces = l2Network.interfaces();
return interfaces.stream()
.map(this::hostsFromInterface)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
private Set<Host> hostsFromInterface(Interface iface) {
return hostService.getConnectedHosts(iface.connectPoint())
.stream()
.filter(host -> host.vlan().equals(iface.vlan()))
.collect(Collectors.toSet());
}
private Key buildKey(String l2NetworkName, String type, ConnectPoint cPoint, MacAddress dstMac) {
return Key.of(l2NetworkName + "-" + type + "-" + cPoint.toString() + "-" + dstMac, l2ForwardAppId);
}
private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encapsulation) {
if (!encapsulation.equals(EncapsulationType.NONE)) {
List<Constraint> newConstraints = new ArrayList<>(constraints);
constraints.stream()
.filter(c -> c instanceof EncapsulationConstraint)
.forEach(newConstraints::remove);
newConstraints.add(new EncapsulationConstraint(encapsulation));
return ImmutableList.copyOf(newConstraints);
}
return constraints;
}
private FilteredConnectPoint buildFilteredConnectedPoint(Interface iface) {
Objects.requireNonNull(iface);
TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
if (iface.vlan() != null && !iface.vlan().equals(VlanId.NONE)) {
trafficSelector.matchVlanId(iface.vlan());
}
return new FilteredConnectPoint(iface.connectPoint(), trafficSelector.build());
}
protected FilteredConnectPoint buildFilteredConnectedPoint(Host host) {
Objects.requireNonNull(host);
TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
if (host.vlan() != null && !host.vlan().equals(VlanId.NONE)) {
trafficSelector.matchVlanId(host.vlan());
}
return new FilteredConnectPoint(host.location(), trafficSelector.build());
}
// Dump command handler
private void dump(String subject, PrintStream out) {
if ("intents".equals(subject)) {
out.println("L2Forward Broadcast Intents:\n");
for (SinglePointToMultiPointIntent intent: bctIntentsMap.values()) {
out.println(" " + intent.key().toString()
+ ": " + intent.selector().criteria()
+ ", [" + intent.filteredIngressPoint().connectPoint()
+ "] -> " + intent.filteredEgressPoints().stream()
.map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet()));
}
out.println("");
out.println("L2Forward Unicast Intents:\n");
for (MultiPointToSinglePointIntent intent: uniIntentsMap.values()) {
out.println(" " + intent.key().toString()
+ ": " + intent.selector().criteria()
+ ", [" + intent.filteredIngressPoints().stream()
.map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet())
+ "] -> " + intent.filteredEgressPoint().connectPoint());
}
out.println("");
out.println("L2Forward Intents to Be Purged:\n");
for (Key key: toBePurgedIntentKeys) {
out.println(" " + key.toString());
}
out.println("");
}
}
// Listener
private class InternalSimpleFabricListener implements SimpleFabricListener {
@Override
public void event(SimpleFabricEvent event) {
switch (event.type()) {
case SIMPLE_FABRIC_UPDATED:
refresh();
checkIntentsPurge();
break;
case SIMPLE_FABRIC_IDLE:
refresh();
checkIntentsPurge();
break;
case SIMPLE_FABRIC_DUMP:
dump(event.subject(), event.out());
break;
default:
// NOTE: nothing to do on SIMPLE_FABRIC_FLUSH
break;
}
}
}
}