blob: 039cd1261ef7f91c555d9a6cec26b6fea197b5dd [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.vpls;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Activate;
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.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.app.ApplicationService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceEvent;
import org.onosproject.incubator.net.intf.InterfaceListener;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.EncapsulationType;
import org.onosproject.net.FilteredConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.Key;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.vpls.config.VplsConfigurationService;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
import static org.onosproject.vpls.IntentInstaller.PREFIX_BROADCAST;
import static org.onosproject.vpls.IntentInstaller.PREFIX_UNICAST;
/**
* Application to create L2 broadcast overlay networks using VLANs.
*/
@Component(immediate = true)
public class Vpls {
static final String VPLS_APP = "org.onosproject.vpls";
private static final String HOST_FCP_NOT_FOUND =
"Filtered connected point for host {} not found";
private static final String HOST_EVENT = "Received HostEvent {}";
private static final String INTF_CONF_EVENT =
"Received InterfaceConfigEvent {}";
private static final String NET_CONF_EVENT =
"Received NetworkConfigEvent {}";
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ApplicationService applicationService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected IntentService intentService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected IntentSynchronizationService intentSynchronizer;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected VplsConfigurationService vplsConfigService;
private final HostListener hostListener = new InternalHostListener();
private final InternalInterfaceListener interfaceListener =
new InternalInterfaceListener();
private final InternalNetworkConfigListener configListener =
new InternalNetworkConfigListener();
private IntentInstaller intentInstaller;
private ApplicationId appId;
@Activate
public void activate() {
appId = coreService.registerApplication(VPLS_APP);
intentInstaller = new IntentInstaller(appId,
intentService,
intentSynchronizer);
applicationService.registerDeactivateHook(appId, () -> {
intentSynchronizer.removeIntentsByAppId(appId);
});
hostService.addListener(hostListener);
interfaceService.addListener(interfaceListener);
configService.addListener(configListener);
setupConnectivity(false);
log.info("Activated");
}
@Deactivate
public void deactivate() {
configService.removeListener(configListener);
intentSynchronizer.removeIntentsByAppId(appId);
log.info("Deactivated");
}
/**
* Sets up connectivity for all VPLSs.
*
* @param isNetworkConfigEvent true if this function is triggered
* by NetworkConfigEvent; false otherwise
*/
private void setupConnectivity(boolean isNetworkConfigEvent) {
SetMultimap<String, Interface> networkInterfaces =
vplsConfigService.ifacesByVplsName();
Set<String> vplsAffectedByApi =
new HashSet<>(vplsConfigService.vplsAffectedByApi());
if (isNetworkConfigEvent && vplsAffectedByApi.isEmpty()) {
vplsAffectedByApi.addAll(vplsConfigService.vplsNamesOld());
}
networkInterfaces.asMap().forEach((vplsName, interfaces) -> {
Set<Host> hosts = Sets.newHashSet();
interfaces.forEach(intf -> {
// Add hosts that belongs to the specific VPLS
hostService.getConnectedHosts(intf.connectPoint())
.stream()
.filter(host -> host.vlan().equals(intf.vlan()))
.forEach(hosts::add);
});
EncapsulationType encap =
vplsConfigService.encap(vplsName);
setupConnectivity(vplsName, interfaces, hosts, encap,
vplsAffectedByApi.contains(vplsName));
vplsAffectedByApi.remove(vplsName);
});
if (!vplsAffectedByApi.isEmpty()) {
for (String vplsName : vplsAffectedByApi) {
withdrawIntents(vplsName, Lists.newArrayList());
}
}
}
/**
* Sets up connectivity for specific VPLS.
*
* @param vplsName the VPLS name
* @param interfaces the interfaces that belong to the VPLS
* @param hosts the hosts that belong to the VPLS
* @param encap the encapsulation type
* @param affectedByApi true if this function is triggered from the APIs;
* false otherwise
*/
private void setupConnectivity(String vplsName,
Collection<Interface> interfaces,
Set<Host> hosts,
EncapsulationType encap,
boolean affectedByApi) {
List<Intent> intents = Lists.newArrayList();
List<Key> keys = Lists.newArrayList();
Set<FilteredConnectPoint> fcPoints = buildFCPoints(interfaces);
intents.addAll(buildBroadcastIntents(
vplsName, fcPoints, encap, affectedByApi));
intents.addAll(buildUnicastIntents(
vplsName, hosts, fcPoints, encap, affectedByApi));
if (affectedByApi) {
intents.forEach(intent -> keys.add(intent.key()));
withdrawIntents(vplsName, keys);
}
intentInstaller.submitIntents(intents);
}
/**
* Withdraws intents belonging to a VPLS, given a VPLS name.
*
* @param vplsName the VPLS name
* @param keys the keys of the intents to be installed
*/
private void withdrawIntents(String vplsName, List<Key> keys) {
List<Intent> intents = Lists.newArrayList();
intentInstaller.getIntentsFromVpls(vplsName)
.forEach(intent -> {
if (!keys.contains(intent.key())) {
intents.add(intent);
}
});
intentInstaller.withdrawIntents(intents);
}
/**
* Sets up broadcast intents between any given filtered connect point.
*
* @param vplsName the VPLS name
* @param fcPoints the set of filtered connect points
* @param encap the encapsulation type
* @param affectedByApi true if the function triggered from APIs;
* false otherwise
* @return the set of broadcast intents
*/
private Set<Intent> buildBroadcastIntents(String vplsName,
Set<FilteredConnectPoint> fcPoints,
EncapsulationType encap,
boolean affectedByApi) {
Set<Intent> intents = Sets.newHashSet();
fcPoints.forEach(point -> {
Set<FilteredConnectPoint> otherPoints =
fcPoints.stream()
.filter(fcp -> !fcp.equals(point))
.collect(Collectors.toSet());
Key brcKey = intentInstaller.buildKey(PREFIX_BROADCAST,
point.connectPoint(),
vplsName,
MacAddress.BROADCAST);
if ((!intentInstaller.intentExists(brcKey) || affectedByApi)
&& !otherPoints.isEmpty()) {
intents.add(intentInstaller.buildBrcIntent(brcKey,
point,
otherPoints,
encap));
}
});
return ImmutableSet.copyOf(intents);
}
/**
* Sets up unicast intents between any given filtered connect point.
*
* @param vplsName the VPLS name
* @param hosts the set of destination hosts
* @param fcPoints the set of filtered connect points
* @param encap the encapsulation type
* @param affectedByApi true if the function triggered from APIs;
* false otherwise
* @return the set of unicast intents
*/
private Set<Intent> buildUnicastIntents(String vplsName,
Set<Host> hosts,
Set<FilteredConnectPoint> fcPoints,
EncapsulationType encap,
boolean affectedByApi) {
Set<Intent> intents = Sets.newHashSet();
hosts.forEach(host -> {
FilteredConnectPoint hostPoint = getHostPoint(host, fcPoints);
if (hostPoint == null) {
log.warn(HOST_FCP_NOT_FOUND, host);
return;
}
Set<FilteredConnectPoint> otherPoints =
fcPoints.stream()
.filter(fcp -> !fcp.equals(hostPoint))
.collect(Collectors.toSet());
Key uniKey = intentInstaller.buildKey(PREFIX_UNICAST,
host.location(),
vplsName,
host.mac());
if ((!intentInstaller.intentExists(uniKey) || affectedByApi) &&
!otherPoints.isEmpty()) {
intents.add(intentInstaller.buildUniIntent(uniKey,
otherPoints,
hostPoint,
host,
encap));
}
});
return ImmutableSet.copyOf(intents);
}
/**
* Finds the filtered connect point a host is attached to.
*
* @param host the target host
* @param fcps the filtered connected points
* @return null if not found; the filtered connect point otherwise
*/
private FilteredConnectPoint getHostPoint(Host host,
Set<FilteredConnectPoint> fcps) {
return fcps.stream()
.filter(fcp -> fcp.connectPoint().equals(host.location()))
.filter(fcp -> {
VlanIdCriterion vlanCriterion =
(VlanIdCriterion) fcp.trafficSelector().
getCriterion(Criterion.Type.VLAN_VID);
return vlanCriterion != null &&
vlanCriterion.vlanId().equals(host.vlan());
})
.findFirst()
.orElse(null);
}
/**
* Computes a set of filtered connect points from a list of given interfaces.
*
* @param interfaces the interfaces to compute
* @return the set of filtered connect points
*/
private Set<FilteredConnectPoint> buildFCPoints(Collection<Interface> interfaces) {
// Build all filtered connected points in the VPLS
return interfaces
.stream()
.map(intf -> {
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
if (!intf.vlan().equals(VlanId.NONE)) {
selectorBuilder.matchVlanId(intf.vlan());
}
return new FilteredConnectPoint(intf.connectPoint(),
selectorBuilder.build());
})
.collect(Collectors.toSet());
}
/**
* Listener for host events.
*/
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
log.debug(HOST_EVENT, event);
switch (event.type()) {
case HOST_ADDED:
case HOST_UPDATED:
case HOST_REMOVED:
setupConnectivity(false);
break;
default:
break;
}
}
}
/**
* Listener for interface configuration events.
*/
private class InternalInterfaceListener implements InterfaceListener {
@Override
public void event(InterfaceEvent event) {
log.debug(INTF_CONF_EVENT, event);
switch (event.type()) {
case INTERFACE_ADDED:
case INTERFACE_UPDATED:
case INTERFACE_REMOVED:
setupConnectivity(false);
break;
default:
break;
}
}
}
/**
* Listener for VPLS configuration events.
*/
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (event.configClass() == VplsConfigurationService.CONFIG_CLASS) {
log.debug(NET_CONF_EVENT, event.configClass());
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
case CONFIG_REMOVED:
setupConnectivity(true);
break;
default:
break;
}
}
}
}
}