[ONOS-5283] Arbitrary connect points, support multiple vlans
Change-Id: I9bd3536c08dfd8a637293460395de7e2a1dc1dc1
diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/Vpls.java b/apps/vpls/src/main/java/org/onosproject/vpls/Vpls.java
index 2951d13..4e4bd4b 100644
--- a/apps/vpls/src/main/java/org/onosproject/vpls/Vpls.java
+++ b/apps/vpls/src/main/java/org/onosproject/vpls/Vpls.java
@@ -15,10 +15,10 @@
*/
package org.onosproject.vpls;
-import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
-
-import org.apache.commons.lang3.tuple.Pair;
+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;
@@ -29,29 +29,56 @@
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.ConnectPoint;
+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.Map;
+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 VLAN.
*/
@Component(immediate = true)
public class Vpls {
- protected static final String VPLS_APP = "org.onosproject.vpls";
+ /**
+ * Application name of 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());
@@ -73,11 +100,20 @@
@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;
@@ -97,111 +133,250 @@
hostService.addListener(hostListener);
interfaceService.addListener(interfaceListener);
+ configService.addListener(configListener);
- setupConnectivity();
+ setupConnectivity(false);
log.info("Activated");
}
@Deactivate
public void deactivate() {
+ configService.removeListener(configListener);
intentSynchronizer.removeIntentsByAppId(appId);
log.info("Deactivated");
}
- protected void setupConnectivity() {
- /*
- * Parse Configuration and get Connect Point by VlanId.
- */
- SetMultimap<VlanId, ConnectPoint> confCPointsByVlan = getConfigCPoints();
+ /**
+ * 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.getVplsNetworks();
- /*
- * Check that configured Connect Points have hosts attached and
- * associate their Mac Address to the Connect Points configured.
- */
- SetMultimap<VlanId, Pair<ConnectPoint, MacAddress>> confHostPresentCPoint =
- pairAvailableHosts(confCPointsByVlan);
+ Set<String> vplsAffectedByApi =
+ new HashSet<>(vplsConfigService.getVplsAffectedByApi());
- /*
- * Create and submit intents between the Connect Points.
- * Intents for broadcast between all the configured Connect Points.
- * Intents for unicast between all the configured Connect Points with
- * hosts attached.
- */
- intentInstaller.installIntents(confHostPresentCPoint);
+ if (isNetworkConfigEvent && vplsAffectedByApi.isEmpty()) {
+ vplsAffectedByApi.addAll(vplsConfigService.getOldVpls());
+ }
+ networkInterfaces.asMap().forEach((networkName, 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);
+ });
+
+ setupConnectivity(networkName, interfaces, hosts,
+ vplsAffectedByApi.contains(networkName));
+ vplsAffectedByApi.remove(networkName);
+ });
+
+ if (!vplsAffectedByApi.isEmpty()) {
+ for (String networkName:vplsAffectedByApi) {
+ withdrawIntents(networkName, Lists.newArrayList());
+ }
+ }
}
/**
- * Computes the list of configured interfaces with a VLAN Id.
+ * Sets up connectivity for specific VPLS.
*
- * @return the interfaces grouped by vlan id
+ * @param networkName the VPLS name
+ * @param interfaces the interfaces that belong to the VPLS
+ * @param hosts the hosts that belong to the VPLS
+ * @param affectedByApi true if this function is triggered from the APIs;
+ * false otherwise
*/
- protected SetMultimap<VlanId, ConnectPoint> getConfigCPoints() {
- log.debug("Checking interface configuration");
+ private void setupConnectivity(String networkName,
+ Collection<Interface> interfaces,
+ Set<Host> hosts,
+ boolean affectedByApi) {
+ List<Intent> intents = Lists.newArrayList();
+ List<Key> keys = Lists.newArrayList();
+ Set<FilteredConnectPoint> fcPoints = buildFCPoints(interfaces);
- SetMultimap<VlanId, ConnectPoint> confCPointsByVlan =
- HashMultimap.create();
+ intents.addAll(buildUnicastIntents(
+ networkName, hosts, fcPoints, affectedByApi));
+ intents.addAll(buildBroadcastIntents(
+ networkName, fcPoints, affectedByApi));
- interfaceService.getInterfaces()
- .stream()
- .filter(intf -> intf.ipAddressesList().isEmpty())
- .forEach(intf -> confCPointsByVlan.put(intf.vlan(), intf.connectPoint()));
- return confCPointsByVlan;
+ if (affectedByApi) {
+ intents.forEach(intent -> keys.add(intent.key()));
+ withdrawIntents(networkName, keys);
+ }
+
+ intentInstaller.submitIntents(intents);
}
/**
- * Checks if for any ConnectPoint configured there's an host presents
- * and in case it associates them together.
+ * Withdraws intents belonging to a VPLS, given a VPLS name.
*
- * @param confCPointsByVlan the configured ConnectPoints grouped by VLAN Id
- * @return the configured ConnectPoints with eventual hosts associated.
+ * @param networkName the VPLS name
+ * @param keys the keys of the intents to be installed
*/
- protected SetMultimap<VlanId, Pair<ConnectPoint, MacAddress>> pairAvailableHosts(
- SetMultimap<VlanId, ConnectPoint> confCPointsByVlan) {
- log.debug("Binding connected hosts MAC addresses");
+ private void withdrawIntents(String networkName,
+ List<Key> keys) {
+ List<Intent> intents = Lists.newArrayList();
- SetMultimap<VlanId, Pair<ConnectPoint, MacAddress>> confHostPresentCPoint =
- HashMultimap.create();
+ intentInstaller.getIntentsFromVpls(networkName)
+ .forEach(intent -> {
+ if (!keys.contains(intent.key())) {
+ intents.add(intent);
+ }
+ });
- confCPointsByVlan.entries()
- .forEach(e -> bindMacAddr(e, confHostPresentCPoint));
-
- return confHostPresentCPoint;
+ intentInstaller.withdrawIntents(intents);
}
- // Bind VLAN Id with hosts and connect points
- private void bindMacAddr(Map.Entry<VlanId, ConnectPoint> e,
- SetMultimap<VlanId, Pair<ConnectPoint,
- MacAddress>> confHostPresentCPoint) {
- VlanId vlanId = e.getKey();
- ConnectPoint cp = e.getValue();
- Set<Host> connectedHosts = hostService.getConnectedHosts(cp);
- connectedHosts.forEach(host -> {
- if (host.vlan().equals(vlanId)) {
- confHostPresentCPoint.put(vlanId, Pair.of(cp, host.mac()));
- } else {
- confHostPresentCPoint.put(vlanId, Pair.of(cp, null));
+ /**
+ * Sets up broadcast intents between any given filtered connect point.
+ *
+ * @param networkName the VPLS name
+ * @param fcPoints the set of filtered connect points
+ * @param affectedByApi true if the function triggered from APIs;
+ * false otherwise
+ * @return the set of broadcast intents
+ */
+ private Set<Intent> buildBroadcastIntents(String networkName,
+ Set<FilteredConnectPoint> fcPoints,
+ 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(),
+ networkName,
+ MacAddress.BROADCAST);
+
+ if ((!intentInstaller.intentExists(brcKey) || affectedByApi) &&
+ !otherPoints.isEmpty()) {
+ intents.add(intentInstaller.buildBrcIntent(brcKey,
+ point,
+ otherPoints));
}
});
- if (connectedHosts.isEmpty()) {
- confHostPresentCPoint.put(vlanId, Pair.of(cp, null));
- }
+
+ return ImmutableSet.copyOf(intents);
+ }
+
+ /**
+ * Sets up unicast intents between any given filtered connect point.
+ *
+ * @param networkName the VPLS name
+ * @param hosts the set of destination hosts
+ * @param fcPoints the set of filtered connect points
+ * @param affectedByApi true if the function triggered from APIs;
+ * false otherwise
+ * @return the set of unicast intents
+ */
+ private Set<Intent> buildUnicastIntents(String networkName,
+ Set<Host> hosts,
+ Set<FilteredConnectPoint> fcPoints,
+ 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(),
+ networkName,
+ host.mac());
+
+ if ((!intentInstaller.intentExists(uniKey) || affectedByApi) &&
+ !otherPoints.isEmpty()) {
+ intents.add(intentInstaller.buildUniIntent(uniKey,
+ otherPoints,
+ hostPoint,
+ host));
+ }
+ });
+
+ 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 network
+ 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.
*/
- class InternalHostListener implements HostListener {
+ private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
- log.debug("Received HostEvent {}", event);
+ log.debug(HOST_EVENT, event);
switch (event.type()) {
case HOST_ADDED:
case HOST_UPDATED:
case HOST_REMOVED:
- setupConnectivity();
+ setupConnectivity(false);
break;
+
default:
break;
}
@@ -214,16 +389,39 @@
private class InternalInterfaceListener implements InterfaceListener {
@Override
public void event(InterfaceEvent event) {
- log.debug("Received InterfaceConfigEvent {}", event);
+ log.debug(INTF_CONF_EVENT, event);
switch (event.type()) {
case INTERFACE_ADDED:
case INTERFACE_UPDATED:
case INTERFACE_REMOVED:
- setupConnectivity();
+ 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;
+ }
+ }
+ }
+ }
}