diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/IntentInstaller.java b/apps/vpls/src/main/java/org/onosproject/vpls/IntentInstaller.java index 79ed71351c..52a3924cc7 100644 --- a/apps/vpls/src/main/java/org/onosproject/vpls/IntentInstaller.java +++ b/apps/vpls/src/main/java/org/onosproject/vpls/IntentInstaller.java @@ -15,18 +15,18 @@ */ package org.onosproject.vpls; -import com.google.common.collect.SetMultimap; -import org.apache.commons.lang3.tuple.Pair; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import org.onlab.packet.MacAddress; -import org.onlab.packet.VlanId; import org.onosproject.core.ApplicationId; import org.onosproject.net.ConnectPoint; +import org.onosproject.net.FilteredConnectPoint; +import org.onosproject.net.Host; 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.intent.Intent; import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentState; import org.onosproject.net.intent.Key; import org.onosproject.net.intent.MultiPointToSinglePointIntent; import org.onosproject.net.intent.SinglePointToMultiPointIntent; @@ -34,24 +34,37 @@ import org.onosproject.routing.IntentSynchronizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; /** * Synchronizes intents between the in-memory intent store and the * IntentService. */ public class IntentInstaller { + private static final String SUBMIT = + "Submitting intents to the Intent Synchronizer"; + private static final String WITHDRAW = + "Withdrawing intents to the Intent Synchronizer"; + private static final String SP2MP = + "Building sp2mp intent from {}"; + private static final String MP2SP = + "Building mp2sp intent to {}"; + private static final Logger log = LoggerFactory.getLogger( IntentInstaller.class); private static final int PRIORITY_OFFSET = 1000; - private static final String PREFIX_BROADCAST = "brc"; - private static final String PREFIX_UNICAST = "uni"; + private static final Set WITHDRAWN_INTENT_STATES = + ImmutableSet.of(IntentState.WITHDRAWN, + IntentState.WITHDRAW_REQ, + IntentState.WITHDRAWING); + + static final String PREFIX_BROADCAST = "brc"; + static final String PREFIX_UNICAST = "uni"; + static final String DASH = "-"; private final ApplicationId appId; private final IntentSynchronizationService intentSynchronizer; @@ -71,174 +84,151 @@ public class IntentInstaller { this.intentSynchronizer = intentSynchronizer; } - /** - * Formats the requests for creating and submit intents. - * Single Points to Multi Point intents are created for all the configured - * Connect Points. Multi Point to Single Point intents are created for - * Connect Points configured that have hosts attached. - * - * @param confHostPresentCPoint A map of Connect Points with the eventual - * MAC address of the host attached, by VLAN - */ - protected void installIntents(SetMultimap> confHostPresentCPoint) { - List intents = new ArrayList<>(); - - confHostPresentCPoint.keySet() - .stream() - .filter(vlanId -> confHostPresentCPoint.get(vlanId) != null) - .forEach(vlanId -> { - Set> cPoints = - confHostPresentCPoint.get(vlanId); - cPoints.forEach(cPoint -> { - MacAddress mac = cPoint.getValue(); - ConnectPoint src = cPoint.getKey(); - Set dsts = cPoints.stream() - .map(Pair::getKey) - .filter(cp -> !cp.equals(src)) - .collect(Collectors.toSet()); - Key brcKey = buildKey(PREFIX_BROADCAST, src, vlanId); - - if (dsts.isEmpty()) { - return; - } - - intents.add(buildBrcIntent(brcKey, src, dsts, vlanId)); - - if (mac != null && countMacInCPoints(cPoints) > 1) { - Key uniKey = buildKey(PREFIX_UNICAST, src, vlanId); - MultiPointToSinglePointIntent uniIntent = - buildUniIntent(uniKey, - dsts, - src, - vlanId, - mac); - intents.add(uniIntent); - } - }); - }); - submitIntents(intents); - } - /** * Requests to install the intents passed as argument to the Intent Service. * * @param intents intents to be submitted */ - private void submitIntents(Collection intents) { - log.debug("Submitting intents to the Intent Synchronizer"); - intents.forEach(intent -> { - intentSynchronizer.submit(intent); - }); + protected void submitIntents(Collection intents) { + log.debug(SUBMIT); + intents.forEach(intentSynchronizer::submit); } /** - * Builds a Single Point to Multi Point intent. + * Requests to withdraw the intents passed as argument to the Intent Service. * - * @param src The source Connect Point - * @param dsts The destination Connect Points - * @return Single Point to Multi Point intent generated. + * @param intents intents to be withdraw */ - private SinglePointToMultiPointIntent buildBrcIntent(Key key, - ConnectPoint src, - Set dsts, - VlanId vlanId) { - log.debug("Building p2mp intent from {}", src); + protected void withdrawIntents(Collection intents) { + log.debug(WITHDRAW); + intents.forEach(intentSynchronizer::withdraw); + } + + /** + * Returns list of intents belongs to a VPLS. + * + * @param name required VPLS network name + * @return list of intents belongs to a VPLS + */ + protected List getIntentsFromVpls(String name) { + List intents = Lists.newArrayList(); + + intentService.getIntents().forEach(intent -> { + if (intent.key().toString().startsWith(name)) { + intents.add(intent); + } + }); + + return intents; + } + + /** + * Builds a broadcast intent. + * + * @param key key to identify the intent + * @param src the source connect point + * @param dsts the destination connect points + * @return the generated single-point to multi-point intent + */ + protected SinglePointToMultiPointIntent buildBrcIntent(Key key, + FilteredConnectPoint src, + Set dsts) { + log.debug(SP2MP, src); SinglePointToMultiPointIntent intent; - TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); - - TrafficSelector.Builder builder = DefaultTrafficSelector.builder() + TrafficSelector selector = DefaultTrafficSelector.builder() .matchEthDst(MacAddress.BROADCAST) - .matchVlanId(vlanId); - - TrafficSelector selector = builder.build(); + .build(); intent = SinglePointToMultiPointIntent.builder() .appId(appId) .key(key) .selector(selector) - .treatment(treatment) - .ingressPoint(src) - .egressPoints(dsts) + .filteredIngressPoint(src) + .filteredEgressPoints(dsts) .priority(PRIORITY_OFFSET) .build(); return intent; } /** - * Builds a Multi Point to Single Point intent. + * Builds a unicast intent. * - * @param srcs The source Connect Points - * @param dst The destination Connect Point - * @return Multi Point to Single Point intent generated. + * @param key key to identify the intent + * @param srcs the source Connect Points + * @param dst the destination Connect Point + * @param host destination Host + * @return the generated multi-point to single-point intent */ - private MultiPointToSinglePointIntent buildUniIntent(Key key, - Set srcs, - ConnectPoint dst, - VlanId vlanId, - MacAddress mac) { - log.debug("Building mp2p intent to {}", dst); + protected MultiPointToSinglePointIntent buildUniIntent(Key key, + Set srcs, + FilteredConnectPoint dst, + Host host) { + log.debug(MP2SP, dst); - MultiPointToSinglePointIntent intent; + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthDst(host.mac()) + .build(); - TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); - TrafficSelector.Builder builder = DefaultTrafficSelector.builder() - .matchEthDst(mac) - .matchVlanId(vlanId); - - TrafficSelector selector = builder.build(); - - intent = MultiPointToSinglePointIntent.builder() + return MultiPointToSinglePointIntent.builder() .appId(appId) .key(key) .selector(selector) - .treatment(treatment) - .ingressPoints(srcs) - .egressPoint(dst) + .filteredIngressPoints(srcs) + .filteredEgressPoint(dst) .priority(PRIORITY_OFFSET) .build(); - return intent; + } /** - * Builds an intent Key for either for a Single Point to Multi Point or - * Multi Point to Single Point intent, based on a prefix that defines + * Builds an intent Key for either for a single-point to multi-point or + * multi-point to single-point intent, based on a prefix that defines * the type of intent, the single connection point representing the source - * or the destination and the vlan id representing the network. + * or the destination and the VLAN identifier representing the network. * - * @param cPoint the source or destination connect point - * @param vlanId the network vlan id - * @param prefix prefix string - * @return + * @param prefix key prefix + * @param cPoint connect point for single source/destination + * @param networkName VPLS network name + * @param hostMac source/destination mac address + * @return key to identify the intent */ - private Key buildKey(String prefix, ConnectPoint cPoint, VlanId vlanId) { - String keyString = new StringBuilder() - .append(prefix) - .append("-") - .append(cPoint.deviceId()) - .append("-") - .append(cPoint.port()) - .append("-") - .append(vlanId) - .toString(); + protected Key buildKey(String prefix, + ConnectPoint cPoint, + String networkName, + MacAddress hostMac) { + String keyString = networkName + + DASH + + prefix + + DASH + + cPoint.deviceId() + + DASH + + cPoint.port() + + DASH + + hostMac; return Key.of(keyString, appId); } /** - * Counts the number of mac addresses associated to a specific list of - * ConnectPoint. + * Returns true if the specified intent exists; false otherwise. * - * @param cPoints Set of ConnectPoints, eventually bound to the MAC of the - * host attached - * @return number of mac addresses found. + * @param intentKey intent key + * @return true if the intent exists, false otherwise */ - private int countMacInCPoints(Set> cPoints) { - return (int) cPoints.stream().filter(p -> p.getValue() != null).count(); - } + protected boolean intentExists(Key intentKey) { + if (intentService.getIntent(intentKey) == null) { + return false; + } + // Intent does not exist if intent withdrawn + IntentState currentIntentState = intentService.getIntentState(intentKey); + if (WITHDRAWN_INTENT_STATES.contains(currentIntentState)) { + return false; + } + + return true; + } } 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 2951d13e3d..4e4bd4bf9d 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.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.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 @@ public class Vpls { @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 @@ public class Vpls { 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 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 networkInterfaces = + vplsConfigService.getVplsNetworks(); - /* - * Check that configured Connect Points have hosts attached and - * associate their Mac Address to the Connect Points configured. - */ - SetMultimap> confHostPresentCPoint = - pairAvailableHosts(confCPointsByVlan); + Set 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 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 getConfigCPoints() { - log.debug("Checking interface configuration"); + private void setupConnectivity(String networkName, + Collection interfaces, + Set hosts, + boolean affectedByApi) { + List intents = Lists.newArrayList(); + List keys = Lists.newArrayList(); + Set fcPoints = buildFCPoints(interfaces); - SetMultimap 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> pairAvailableHosts( - SetMultimap confCPointsByVlan) { - log.debug("Binding connected hosts MAC addresses"); + private void withdrawIntents(String networkName, + List keys) { + List intents = Lists.newArrayList(); - SetMultimap> 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 e, - SetMultimap> confHostPresentCPoint) { - VlanId vlanId = e.getKey(); - ConnectPoint cp = e.getValue(); - Set 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 buildBroadcastIntents(String networkName, + Set fcPoints, + boolean affectedByApi) { + Set intents = Sets.newHashSet(); + fcPoints.forEach(point -> { + Set 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 buildUnicastIntents(String networkName, + Set hosts, + Set fcPoints, + boolean affectedByApi) { + Set intents = Sets.newHashSet(); + hosts.forEach(host -> { + FilteredConnectPoint hostPoint = getHostPoint(host, fcPoints); + + if (hostPoint == null) { + log.warn(HOST_FCP_NOT_FOUND, host); + return; + } + + Set 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 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 buildFCPoints(Collection 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 @@ public class Vpls { 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; + } + } + } + } } diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsConfig.java b/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsConfig.java new file mode 100644 index 0000000000..f8323efe57 --- /dev/null +++ b/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsConfig.java @@ -0,0 +1,194 @@ +/* + * 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.config; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Sets; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.config.Config; + +import java.util.Set; + +/** + * Configuration object for VPLS config. + */ +public class VplsConfig extends Config { + private static final String VPLS = "vplsNetworks"; + private static final String NAME = "name"; + private static final String INTERFACE = "interfaces"; + + /** + * Returns a set of configured VPLSs. + * + * @return set of VPLSs + */ + public Set vplsNetworks() { + Set vpls = Sets.newHashSet(); + + JsonNode vplsNode = object.get(VPLS); + + if (vplsNode == null) { + return vpls; + } + + vplsNode.forEach(jsonNode -> { + Set ifaces = Sets.newHashSet(); + jsonNode.path(INTERFACE).forEach(ifacesNode -> + ifaces.add(ifacesNode.asText()) + ); + + String name = jsonNode.get(NAME).asText(); + + vpls.add(new VplsNetworkConfig(name, ifaces)); + }); + + return vpls; + } + + /** + * Returns the VPLS configuration given a VPLS name. + * + * @param name the VPLS name + * @return the VPLS configuration if it exists; null otherwise + */ + public VplsNetworkConfig getVplsWithName(String name) { + for (VplsNetworkConfig vpls : vplsNetworks()) { + if (vpls.name().equals(name)) { + return vpls; + } + } + return null; + } + + /** + * Adds a VPLS to the configuration. + * + * @param name the name of the VPLS to be added + */ + public void addVpls(VplsNetworkConfig name) { + ObjectNode vplsNode = JsonNodeFactory.instance.objectNode(); + + vplsNode.put(NAME, name.name()); + + ArrayNode ifacesNode = vplsNode.putArray(INTERFACE); + name.ifaces().forEach(ifacesNode::add); + + ArrayNode vplsArray = vplsNetworks().isEmpty() ? + initVplsConfiguration() : (ArrayNode) object.get(VPLS); + vplsArray.add(vplsNode); + } + + /** + * Removes a VPLS from the configuration. + * + * @param name the name of the VPLS to be removed + */ + public void removeVpls(String name) { + ArrayNode vplsArray = (ArrayNode) object.get(VPLS); + + for (int i = 0; i < vplsArray.size(); i++) { + if (vplsArray.get(i).hasNonNull(NAME) && + vplsArray.get(i).get(NAME).asText().equals(name)) { + vplsArray.remove(i); + return; + } + } + } + + /** + * Finds a VPLS with a given network interface. + * + * @param iface the network interface + * @return the VPLS if found; null otherwise + */ + public VplsNetworkConfig getVplsFromInterface(String iface) { + for (VplsNetworkConfig vpls : vplsNetworks()) { + if (vpls.isAttached(iface)) { + return vpls; + } + } + return null; + } + + /** + * Adds a network interface to a VPLS. + * + * @param name the name of the VPLS + * @param iface the network interface to be added + */ + public void addInterfaceToVpls(String name, String iface) { + JsonNode vplsNode = object.get(VPLS); + vplsNode.forEach(jsonNode -> { + + if (hasNamedNode(jsonNode, name)) { + ArrayNode ifacesNode = (ArrayNode) jsonNode.get(INTERFACE); + for (int i = 0; i < ifacesNode.size(); i++) { + if (ifacesNode.get(i).asText().equals(iface)) { + return; // Interface already exists. + } + } + ifacesNode.add(iface); + } + }); + } + + /** + * Removes a network interface from a VPLS. + * + * @param name the name of the VPLS + * @param iface the network interface to be removed + */ + public void removeInterfaceFromVpls(VplsNetworkConfig name, String iface) { + JsonNode vplsNode = object.get(VPLS); + vplsNode.forEach(jsonNode -> { + if (hasNamedNode(jsonNode, name.name())) { + ArrayNode ifacesNode = (ArrayNode) jsonNode.get(INTERFACE); + for (int i = 0; i < ifacesNode.size(); i++) { + if (ifacesNode.get(i).asText().equals(iface)) { + ifacesNode.remove(i); + return; + } + } + } + }); + } + + /** + * States if a JSON node has a "name" attribute and if the value is equal to + * the name given. + * + * @param jsonNode the JSON node + * @param name the node name + * @return true if the JSON node has a "name" attribute with value equal to + * the name given; false otherwise + */ + private boolean hasNamedNode(JsonNode jsonNode, String name) { + return jsonNode.hasNonNull(NAME) && + jsonNode.get(NAME).asText().equals(name); + } + + /** + * Creates an empty VPLS configuration. + * + * @return empty ArrayNode to store the VPLS configuration + */ + private ArrayNode initVplsConfiguration() { + return object.putArray(VPLS); + } +} diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsConfigurationService.java b/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsConfigurationService.java new file mode 100644 index 0000000000..16cd4c46af --- /dev/null +++ b/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsConfigurationService.java @@ -0,0 +1,120 @@ +/* + * 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.config; + +import com.google.common.collect.SetMultimap; +import org.onlab.packet.VlanId; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.net.ConnectPoint; + +import java.util.Set; + +/** + * Provides information about the VPLS configuration. + */ +public interface VplsConfigurationService { + Class CONFIG_CLASS = VplsConfig.class; + + /** + * Adds a VPLS to the configuration. + * + * @param name the name of the VPLS + * @param ifaces the interfaces associated with the VPLS + */ + void addVpls(String name, Set ifaces); + + /** + * Removes a VPLS from the configuration. + * + * @param name the name of the VPLS to be removed + */ + void removeVpls(String name); + + /** + * Adds a network interface to a VPLS. + * + * @param name the name of the VPLS + * @param iface the network interface to be added to the VPLS + */ + void addInterfaceToVpls(String name, String iface); + + /** + * Removes a network interface from a VPLS. + * + * @param iface the network interface to be removed from the VPLS + */ + void removeInterfaceFromVpls(String iface); + + /** + * Cleans up the VPLS configuration. Removes all VPLSs. + */ + void cleanVpls(); + + /** + * Retrieves the VPLS names modified from CLI. + * + * @return a set of VPLS names modified from CLI + */ + Set getVplsAffectedByApi(); + // TODO Removes this function after intent framework fix race condition + + /** + * Retrieves the interfaces from the VPLS configuration. + * + * @return a set of interfaces contained in the VPLS configuration + */ + Set getAllInterfaces(); + + /** + * Retrieves the interfaces belonging to the VPLS. + * + * @param name the name of the VPLS + * @return a set of interfaces belonging to the VPLS + */ + Set getVplsInterfaces(String name); + + /** + * Retrieves all VPLS names. + * + * @return a set of VPLS names + */ + Set getAllVpls(); + + /** + * Retrieves all VPLS names from the old config. + * + * @return a set of VPLS names + */ + Set getOldVpls(); + // TODO Removes this function after intent framework fix race condition + + /** + * Retrieves the VPLS names and associated interfaces from the configuration. + * + * @return a map VPLS names and associated interfaces + */ + SetMultimap getVplsNetworks(); + + /** + * Retrieves a VPLS network given a VLAN Id and a connect point. + * + * @param vlan the VLAN Id + * @param connectPoint the connect point + * @return a map VPLS names and associated interfaces; null otherwise + */ + SetMultimap getVplsNetwork(VlanId vlan, + ConnectPoint connectPoint); +} diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsNetworkConfig.java b/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsNetworkConfig.java new file mode 100644 index 0000000000..b3c721b0b3 --- /dev/null +++ b/apps/vpls/src/main/java/org/onosproject/vpls/config/VplsNetworkConfig.java @@ -0,0 +1,88 @@ +/* + * 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.config; + +import com.google.common.collect.ImmutableSet; + +import java.util.Objects; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Configuration of a VPLS Network. + */ +public class VplsNetworkConfig { + private final String name; + private final Set ifaces; + + /** + * Creates a new VPLS configuration. + * + * @param name the VPLS name + * @param ifaces the interfaces associated with the VPLS + */ + public VplsNetworkConfig(String name, Set ifaces) { + this.name = checkNotNull(name); + this.ifaces = checkNotNull(ImmutableSet.copyOf(ifaces)); + } + + /** + * Returns the name of the VPLS. + * + * @return the name of the VPLS + */ + public String name() { + return name; + } + + /** + * Returns the name of interfaces associated with the VPLS. + * + * @return a set of interface names associated with the VPLS + */ + public Set ifaces() { + return ImmutableSet.copyOf(ifaces); + } + + /** + * States if a given interface is part of a VPLS. + * + * @param iface the interface attached to a VPLS + * @return true if the interface is associated to the VPLS; false otherwise + */ + public boolean isAttached(String iface) { + return ifaces.stream().anyMatch(i -> i.equals(iface)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof VplsNetworkConfig) { + final VplsNetworkConfig that = (VplsNetworkConfig) obj; + return Objects.equals(this.name, that.name) && + Objects.equals(this.ifaces, that.ifaces); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(name, ifaces); + } +} diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/config/impl/VplsConfigurationImpl.java b/apps/vpls/src/main/java/org/onosproject/vpls/config/impl/VplsConfigurationImpl.java new file mode 100644 index 0000000000..e977170a7a --- /dev/null +++ b/apps/vpls/src/main/java/org/onosproject/vpls/config/impl/VplsConfigurationImpl.java @@ -0,0 +1,335 @@ +/* + * 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.config.impl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +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.apache.felix.scr.annotations.Service; +import org.onlab.packet.VlanId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.config.NetworkConfigRegistry; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.basics.SubjectFactories; +import org.onosproject.vpls.config.VplsConfig; +import org.onosproject.vpls.config.VplsNetworkConfig; +import org.onosproject.vpls.config.VplsConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; + +/** + * Implementation of VPLSConfigurationService which reads VPLS configuration + * from the network configuration service. + */ +@Component(immediate = true) +@Service +public class VplsConfigurationImpl implements VplsConfigurationService { + private static final String VPLS_APP = "org.onosproject.vpls"; + private static final String VPLS = "vpls"; + private static final String EMPTY = ""; + private static final String CONFIG_NULL = "VPLS configuration not defined"; + private static final String APP_ID_NULL = "VPLS application ID is null"; + private static final String CONFIG_CHANGED = "VPLS configuration changed: {}"; + private static final String CHECK_CONFIG = + "Checking the interface configuration"; + private static final String NET_CONF_EVENT = + "Received NetworkConfigEvent {}"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigRegistry registry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected InterfaceService interfaceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService configService; + + private final Set vplsAffectedByApi = new HashSet<>(); + + private VplsConfig vplsConfig = new VplsConfig(); + + private SetMultimap ifacesOfVpls = HashMultimap.create(); + private SetMultimap oldIfacesOfVpls = HashMultimap.create(); + private SetMultimap vplsNetworks = HashMultimap.create(); + + private final InternalNetworkConfigListener configListener = + new InternalNetworkConfigListener(); + + private ConfigFactory vplsConfigFactory = + new ConfigFactory( + SubjectFactories.APP_SUBJECT_FACTORY, VplsConfig.class, VPLS) { + @Override + public VplsConfig createConfig() { + return new VplsConfig(); + } + }; + + private ApplicationId vplsAppId; + + @Activate + protected void active() { + configService.addListener(configListener); + registry.registerConfigFactory(vplsConfigFactory); + loadConfiguration(); + log.info("Started"); + } + + @Deactivate + protected void deactive() { + registry.unregisterConfigFactory(vplsConfigFactory); + configService.removeListener(configListener); + log.info("Stopped"); + } + + /** + * Retrieves the VPLS configuration from network configuration. + */ + private void loadConfiguration() { + loadAppId(); + + vplsConfig = configService.getConfig(vplsAppId, VplsConfig.class); + + if (vplsConfig == null) { + log.warn(CONFIG_NULL); + configService.addConfig(vplsAppId, VplsConfig.class); + return; + } + + oldIfacesOfVpls = ifacesOfVpls; + ifacesOfVpls = getConfigInterfaces(); + vplsNetworks = getConfigCPoints(); + log.debug(CONFIG_CHANGED, ifacesOfVpls); + } + + /** + * Retrieves the application identifier from core service. + */ + private void loadAppId() { + vplsAppId = coreService.getAppId(VPLS_APP); + if (vplsAppId == null) { + log.warn(APP_ID_NULL); + } + } + + /** + * Applies a given configuration to the VPLS application. + */ + private void applyConfig(VplsConfig vplsConfig) { + loadAppId(); + configService.applyConfig(vplsAppId, VplsConfig.class, vplsConfig.node()); + } + + /** + * Retrieves the VPLS names and associated interfaces names from the configuration. + * + * @return a map VPLS names and associated interface names + */ + private SetMultimap getConfigInterfaces() { + SetMultimap confIntfByVpls = + HashMultimap.create(); + + vplsConfig.vplsNetworks().forEach(vpls -> { + if (vpls.ifaces().isEmpty()) { + confIntfByVpls.put(vpls.name(), EMPTY); + } else { + vpls.ifaces().forEach(iface -> confIntfByVpls.put(vpls.name(), iface)); + } + }); + + return confIntfByVpls; + } + + /** + * Retrieves the VPLS names and associated interfaces from the configuration. + * + * @return a map VPLS names and associated interfaces + */ + private SetMultimap getConfigCPoints() { + log.debug(CHECK_CONFIG); + + SetMultimap confCPointsByIntf = + HashMultimap.create(); + + ifacesOfVpls.entries().forEach(vpls -> { + interfaceService.getInterfaces() + .stream() + .filter(intf -> intf.ipAddressesList().isEmpty()) + .filter(intf -> intf.name().equals(vpls.getValue())) + .forEach(intf -> confCPointsByIntf.put(vpls.getKey(), intf)); + }); + + return confCPointsByIntf; + } + + /** + * 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: + loadConfiguration(); + break; + + default: + break; + } + } + } + } + + @Override + public void addVpls(String name, Set ifaces) { + VplsNetworkConfig vpls; + + if (ifacesOfVpls.containsKey(name)) { + if (ifaces.isEmpty()) { + return; + } + + ifaces.forEach(iface -> + vplsConfig.addInterfaceToVpls(name, iface)); + } else { + vpls = new VplsNetworkConfig(name, ifaces); + vplsConfig.addVpls(vpls); + } + + vplsAffectedByApi.add(name); + applyConfig(vplsConfig); + } + + @Override + public void removeVpls(String name) { + if (ifacesOfVpls.containsKey(name)) { + vplsConfig.removeVpls(name); + vplsAffectedByApi.add(name); + applyConfig(vplsConfig); + } + } + + @Override + public void addInterfaceToVpls(String name, String iface) { + if (ifacesOfVpls.containsKey(name)) { + vplsConfig.addInterfaceToVpls(name, iface); + vplsAffectedByApi.add(name); + applyConfig(vplsConfig); + } + } + + @Override + public void removeInterfaceFromVpls(String iface) { + if (ifacesOfVpls.containsValue(iface)) { + VplsNetworkConfig vpls = vplsConfig.getVplsFromInterface(iface); + vplsConfig.removeInterfaceFromVpls(vpls, iface); + vplsAffectedByApi.add(vpls.name()); + applyConfig(vplsConfig); + } + } + + @Override + public void cleanVpls() { + ifacesOfVpls.entries().forEach(e -> { + vplsConfig.removeVpls(e.getKey()); + vplsAffectedByApi.add(e.getKey()); + }); + applyConfig(vplsConfig); + } + + @Override + public Set getVplsAffectedByApi() { + Set vplsNames = ImmutableSet.copyOf(vplsAffectedByApi); + + vplsAffectedByApi.clear(); + + return vplsNames; + } + + @Override + public Set getAllInterfaces() { + Set allInterfaces = new HashSet<>(); + vplsNetworks.values().forEach(allInterfaces::add); + + return allInterfaces; + } + + @Override + public Set getVplsInterfaces(String name) { + Set vplsInterfaces = new HashSet<>(); + vplsNetworks.get(name).forEach(vplsInterfaces::add); + + return vplsInterfaces; + } + + @Override + public Set getAllVpls() { + return ifacesOfVpls.keySet(); + } + + @Override + public Set getOldVpls() { + return oldIfacesOfVpls.keySet(); + } + + @Override + public SetMultimap getVplsNetworks() { + return ImmutableSetMultimap.copyOf(vplsNetworks); + } + + @Override + public SetMultimap getVplsNetwork(VlanId vlan, + ConnectPoint connectPoint) { + String vplsNetworkName = + vplsNetworks.entries().stream() + .filter(e -> e.getValue().connectPoint().equals(connectPoint)) + .filter(e -> e.getValue().vlan().equals(vlan)) + .map(e -> e.getKey()) + .findFirst() + .orElse(null); + SetMultimap result = HashMultimap.create(); + if (vplsNetworkName != null && vplsNetworks.containsKey(vplsNetworkName)) { + vplsNetworks.get(vplsNetworkName) + .forEach(intf -> result.put(vplsNetworkName, intf)); + return result; + } + return null; + } +} diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/config/impl/package-info.java b/apps/vpls/src/main/java/org/onosproject/vpls/config/impl/package-info.java new file mode 100644 index 0000000000..3229c7b947 --- /dev/null +++ b/apps/vpls/src/main/java/org/onosproject/vpls/config/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Configuration implementation to create L2 broadcast network using VLAN. + */ +package org.onosproject.vpls.config.impl; diff --git a/apps/vpls/src/main/java/org/onosproject/vpls/config/package-info.java b/apps/vpls/src/main/java/org/onosproject/vpls/config/package-info.java new file mode 100644 index 0000000000..f080044e74 --- /dev/null +++ b/apps/vpls/src/main/java/org/onosproject/vpls/config/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Configuration to create L2 broadcast network using VLAN. + */ +package org.onosproject.vpls.config; diff --git a/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java b/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java index 8f0556c6c7..178e1a85f6 100644 --- a/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java +++ b/apps/vpls/src/test/java/org/onosproject/vpls/VplsTest.java @@ -16,14 +16,20 @@ package org.onosproject.vpls; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; +import com.google.common.collect.Lists; +import com.google.common.collect.ImmutableSetMultimap; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -45,10 +51,12 @@ import org.onosproject.net.Host; import org.onosproject.net.HostId; import org.onosproject.net.HostLocation; import org.onosproject.net.PortNumber; +import org.onosproject.net.FilteredConnectPoint; +import org.onosproject.net.config.NetworkConfigService; 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.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; @@ -63,8 +71,7 @@ import org.onosproject.net.intent.SinglePointToMultiPointIntent; import org.onosproject.net.provider.ProviderId; import org.onosproject.routing.IntentSynchronizationAdminService; import org.onosproject.routing.IntentSynchronizationService; - -import com.google.common.collect.Sets; +import org.onosproject.vpls.config.VplsConfigurationService; import static java.lang.String.format; import static org.easymock.EasyMock.anyObject; @@ -75,33 +82,23 @@ import static org.easymock.EasyMock.replay; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.onosproject.vpls.IntentInstaller.PREFIX_BROADCAST; +import static org.onosproject.vpls.IntentInstaller.PREFIX_UNICAST; + /** * Tests for the {@link Vpls} class. */ public class VplsTest { - - private static final int NUM_DEVICES = 7; - - private static final MacAddress MAC1 = MacAddress.valueOf("00:00:00:00:00:01"); - private static final MacAddress MAC2 = MacAddress.valueOf("00:00:00:00:00:02"); - private static final MacAddress MAC3 = MacAddress.valueOf("00:00:00:00:00:03"); - private static final MacAddress MAC4 = MacAddress.valueOf("00:00:00:00:00:04"); - private static final MacAddress MAC5 = MacAddress.valueOf("00:00:00:00:00:05"); - private static final MacAddress MAC6 = MacAddress.valueOf("00:00:00:00:00:06"); - private static final MacAddress MAC7 = MacAddress.valueOf("00:00:00:00:00:07"); - - private static final Ip4Address IP1 = Ip4Address.valueOf("192.168.1.1"); - private static final Ip4Address IP2 = Ip4Address.valueOf("192.168.1.2"); + private static final String APP_NAME = "org.onosproject.vpls"; + private static final ApplicationId APPID = TestApplicationId.create(APP_NAME); + private static final String DASH = "-"; + private static final int PRIORITY_OFFSET = 1000; + private static final String NET1 = "net1"; + private static final String NET2 = "net2"; + private static final String COMPARE = "Comparing %s to %s"; private static final PortNumber P1 = PortNumber.portNumber(1); - private static final VlanId VLAN1 = VlanId.vlanId((short) 1); - private static final VlanId VLAN2 = VlanId.vlanId((short) 2); - - private static final int PRIORITY_OFFSET = 1000; - private static final String PREFIX_BROADCAST = "brc"; - private static final String PREFIX_UNICAST = "uni"; - private static final DeviceId DID1 = getDeviceId(1); private static final DeviceId DID2 = getDeviceId(2); private static final DeviceId DID3 = getDeviceId(3); @@ -109,37 +106,100 @@ public class VplsTest { private static final DeviceId DID5 = getDeviceId(5); private static final DeviceId DID6 = getDeviceId(6); - private static final ConnectPoint C1 = new ConnectPoint(DID1, P1); - private static final ConnectPoint C2 = new ConnectPoint(DID2, P1); - private static final ConnectPoint C3 = new ConnectPoint(DID3, P1); - private static final ConnectPoint C4 = new ConnectPoint(DID4, P1); - private static final ConnectPoint C5 = new ConnectPoint(DID5, P1); - private static final ConnectPoint C6 = new ConnectPoint(DID6, P1); + private static final ConnectPoint CP1 = new ConnectPoint(DID1, P1); + private static final ConnectPoint CP2 = new ConnectPoint(DID2, P1); + private static final ConnectPoint CP3 = new ConnectPoint(DID3, P1); + private static final ConnectPoint CP4 = new ConnectPoint(DID4, P1); + private static final ConnectPoint CP5 = new ConnectPoint(DID5, P1); + private static final ConnectPoint CP6 = new ConnectPoint(DID6, P1); - private static final HostId HID1 = HostId.hostId(MAC1, VLAN1); - private static final HostId HID2 = HostId.hostId(MAC2, VLAN1); - private static final HostId HID3 = HostId.hostId(MAC3, VLAN1); - private static final HostId HID4 = HostId.hostId(MAC4, VLAN2); - private static final HostId HID5 = HostId.hostId(MAC5, VLAN2); - private static final HostId HID6 = HostId.hostId(MAC6, VLAN2); - private static final HostId HID7 = HostId.hostId(MAC7, VlanId.NONE); + private static final VlanId VLAN100 = VlanId.vlanId((short) 100); + private static final VlanId VLAN200 = VlanId.vlanId((short) 200); + private static final VlanId VLAN300 = VlanId.vlanId((short) 300); - private ApplicationService applicationService; - private CoreService coreService; - private HostListener hostListener; - private Set hostsAvailable; - private HostService hostService; - private IntentService intentService; - private InterfaceService interfaceService; - private Vpls vpls; + private static final MacAddress MAC1 = + MacAddress.valueOf("00:00:00:00:00:01"); + private static final MacAddress MAC2 = + MacAddress.valueOf("00:00:00:00:00:02"); + private static final MacAddress MAC3 = + MacAddress.valueOf("00:00:00:00:00:03"); + private static final MacAddress MAC4 = + MacAddress.valueOf("00:00:00:00:00:04"); + private static final MacAddress MAC5 = + MacAddress.valueOf("00:00:00:00:00:05"); + private static final MacAddress MAC6 = + MacAddress.valueOf("00:00:00:00:00:06"); + private static final MacAddress MAC7 = + MacAddress.valueOf("00:00:00:00:00:07"); - private static final String APP_NAME = "org.onosproject.vpls"; - private static final ApplicationId APPID = TestApplicationId.create(APP_NAME); + private static final Ip4Address IP1 = Ip4Address.valueOf("192.168.1.1"); + private static final Ip4Address IP2 = Ip4Address.valueOf("192.168.1.2"); + + private static final HostId HID1 = HostId.hostId(MAC1, VLAN100); + private static final HostId HID2 = HostId.hostId(MAC2, VLAN100); + private static final HostId HID3 = HostId.hostId(MAC3, VLAN200); + private static final HostId HID4 = HostId.hostId(MAC4, VLAN200); + private static final HostId HID5 = HostId.hostId(MAC5, VLAN300); + private static final HostId HID6 = HostId.hostId(MAC6, VLAN300); + private static final HostId HID7 = HostId.hostId(MAC7, VLAN300); private static final ProviderId PID = new ProviderId("of", "foo"); private static IdGenerator idGenerator; + private final Interface v100h1 = + new Interface("v100h1", CP1, null, null, VLAN100); + private final Interface v100h2 = + new Interface("v100h2", CP2, null, null, VLAN100); + private final Interface v200h1 = + new Interface("v200h1", CP3, null, null, VLAN200); + private final Interface v200h2 = + new Interface("v200h2", CP4, null, null, VLAN200); + private final Interface v300h1 = + new Interface("v300h1", CP5, null, null, VLAN300); + private final Interface v300h2 = + new Interface("v300h2", CP6, null, null, VLAN300); + + private final Host v100host1 = + new DefaultHost(PID, HID1, MAC1, VLAN100, + getLocation(1), Collections.singleton(IP1)); + private final Host v100host2 = + new DefaultHost(PID, HID2, MAC2, VLAN100, + getLocation(2), Sets.newHashSet()); + private final Host v200host1 = + new DefaultHost(PID, HID3, MAC3, VLAN200, + getLocation(3), Collections.singleton(IP2)); + private final Host v200host2 = + new DefaultHost(PID, HID4, MAC4, VLAN200, + getLocation(4), Sets.newHashSet()); + private final Host v300host1 = + new DefaultHost(PID, HID5, MAC5, VLAN300, + getLocation(5), Sets.newHashSet()); + private final Host v300host2 = + new DefaultHost(PID, HID6, MAC6, VLAN300, + getLocation(6), Sets.newHashSet()); + private final Host v300host3 = + new DefaultHost(PID, HID7, MAC7, VLAN300, + getLocation(7), Sets.newHashSet()); + + private final Set avaliableInterfaces = + ImmutableSet.of(v100h1, v100h2, v200h1, v200h2, v300h1, v300h2); + + private final Set avaliableHosts = + ImmutableSet.of(v100host1, v100host2, v200host1, + v200host2, v300host1, v300host2, v300host3); + + private ApplicationService applicationService; + private CoreService coreService; + private HostListener hostListener; + private NetworkConfigService configService; + private Set hostsAvailable; + private HostService hostService; + private IntentService intentService; + private InterfaceService interfaceService; + private VplsConfigurationService vplsConfigService; + private Vpls vpls; + @Before public void setUp() throws Exception { idGenerator = new TestIdGenerator(); @@ -147,6 +207,8 @@ public class VplsTest { applicationService = createMock(ApplicationService.class); + configService = createMock(NetworkConfigService.class); + coreService = createMock(CoreService.class); expect(coreService.registerApplication(APP_NAME)) .andReturn(APPID); @@ -165,12 +227,25 @@ public class VplsTest { expectLastCall().anyTimes(); addIntfConfig(); + SetMultimap vplsNetworks = + HashMultimap.create(); + vplsNetworks.put(NET1, v100h1); + vplsNetworks.put(NET1, v200h1); + vplsNetworks.put(NET1, v300h1); + vplsNetworks.put(NET2, v100h2); + vplsNetworks.put(NET2, v200h2); + vplsNetworks.put(NET2, v300h2); + + vplsConfigService = new TestVplsConfigService(vplsNetworks); + vpls = new Vpls(); vpls.applicationService = applicationService; vpls.coreService = coreService; vpls.hostService = hostService; + vpls.vplsConfigService = vplsConfigService; vpls.intentService = intentService; vpls.interfaceService = interfaceService; + vpls.configService = configService; vpls.intentSynchronizer = intentSynchronizer; } @@ -181,181 +256,189 @@ public class VplsTest { } /** - * Creates the interface configuration. On devices 1, 2 and 3 is configured - * an interface on port 1 with vlan 1. On devices 4, 5 and 6 is configured - * an interface on port 1 with vlan 2. On device 5 no interfaces are - * configured. + * Creates the interface configuration. On devices 1 and 2 is configured + * an interface on port 1 with vlan 100. On devices 3 and 4 is configured + * an interface on port 1 with vlan 200. On device 5 and 6 is configured + * an interface on port 1 with vlan 300. */ private void addIntfConfig() { - Set interfaces = Sets.newHashSet(); - Set vlanOneSet = Sets.newHashSet(); - Set vlanTwoSet = Sets.newHashSet(); + Set interfaces = ImmutableSet.copyOf(avaliableInterfaces); + Set vlanOneSet = ImmutableSet.of(v100h1, v100h2); + Set vlanTwoSet = ImmutableSet.of(v200h1, v200h2); + Set vlanThreeSet = ImmutableSet.of(v300h1, v300h2); - for (int i = 1; i <= NUM_DEVICES - 1; i++) { - ConnectPoint cp = new ConnectPoint(getDeviceId(i), P1); - - Interface intf = - new Interface("intfOne", cp, Collections.emptyList(), null, - VlanId.NONE); - - if (i <= 3) { - intf = new Interface("intfTwo", cp, Collections.emptyList(), - null, VLAN1); - interfaces.add(intf); - vlanOneSet.add(intf); - } else if (i > 3 && i <= 6) { - intf = new Interface("intfThree", cp, Collections.emptyList(), - null, VLAN2); - interfaces.add(intf); - vlanTwoSet.add(intf); - } - expect(interfaceService.getInterfacesByPort(cp)) + avaliableInterfaces.forEach(intf -> { + expect(interfaceService.getInterfacesByPort(intf.connectPoint())) .andReturn(Sets.newHashSet(intf)).anyTimes(); - } - expect(interfaceService.getInterfacesByVlan(VLAN1)) + }); + expect(interfaceService.getInterfacesByVlan(VLAN100)) .andReturn(vlanOneSet).anyTimes(); - expect(interfaceService.getInterfacesByVlan(VLAN2)) + expect(interfaceService.getInterfacesByVlan(VLAN200)) .andReturn(vlanTwoSet).anyTimes(); + expect(interfaceService.getInterfacesByVlan(VLAN300)) + .andReturn(vlanThreeSet).anyTimes(); expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes(); replay(interfaceService); } /** - * Checks the case in which six ports are configured with VLANs but no - * hosts are registered by the HostService. The first three ports have an - * interface configured on VLAN1, the other three on VLAN2. The number of - * intents expected is six: three for VLAN1, three for VLAN2. three sp2mp - * intents, three mp2sp intents. + * Six ports are configured with VLANs but no hosts are registered by the + * HostService. The first three ports have an interface configured on NET1, + * the other three on NET2. The number of intents expected is six: three for + * NET1, three for NET2. Six mp2sp intents. Checks if the number of intents + * submitted to the intent framework is equal to the number of intents + * expected and if all intents are equivalent. */ @Test public void testActivateNoHosts() { vpls.activate(); List expectedIntents = Lists.newArrayList(); - expectedIntents.addAll(generateVlanOneBrc()); - expectedIntents.addAll(generateVlanTwoBrc()); + Set fcPoints; + + fcPoints = buildFCPoints(ImmutableSet.of(v100h1, v200h1, v300h1)); + expectedIntents.addAll(generateVplsBrc(fcPoints, NET1)); + + fcPoints = buildFCPoints(ImmutableSet.of(v100h2, v200h2, v300h2)); + expectedIntents.addAll(generateVplsBrc(fcPoints, NET2)); checkIntents(expectedIntents); } /** - * Checks the case in which six ports are configured with VLANs and four - * hosts are registered by the HostService. The first three ports have an - * interface configured on VLAN1, the other three on VLAN2. The number of - * intents expected is twelve: six for VLAN1, six for VLAN2. six sp2mp - * intents, six mp2sp intents. For VLAN1 IPs are added to demonstrate it - * doesn't influence the number of intents created. + * Six ports are configured with VLANs and six hosts are registered by the + * HostService. The first three ports have an interface configured on NET1, + * the other three on NET2. The number of intents expected is twelve: six + * for VLAN1, six for NET2. six sp2mp intents, six mp2sp intents. For NET1 + * IPs are added to demonstrate this doesn't influence the number of intents + * created. Checks if the number of intents submitted to the intent + * framework is equal to the number of intents expected and if all intents + * are equivalent. */ @Test - public void testFourInterfacesConfiguredHostsPresent() { - Host h1 = new DefaultHost(PID, HID1, MAC1, VLAN1, getLocation(1), - Collections.singleton(IP1)); - Host h2 = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(2), - Collections.singleton(IP2)); - Host h3 = new DefaultHost(PID, HID3, MAC3, VLAN1, getLocation(3), - Collections.EMPTY_SET); - Host h4 = new DefaultHost(PID, HID4, MAC4, VLAN2, getLocation(4), - Collections.EMPTY_SET); - Host h5 = new DefaultHost(PID, HID5, MAC5, VLAN2, getLocation(5), - Collections.EMPTY_SET); - Host h6 = new DefaultHost(PID, HID6, MAC6, VLAN2, getLocation(6), - Collections.EMPTY_SET); - hostsAvailable.addAll(Sets.newHashSet(h1, h2, h3, h4, h5, h6)); + public void testSixInterfacesConfiguredHostsPresent() { + hostsAvailable.addAll(avaliableHosts); vpls.activate(); List expectedIntents = Lists.newArrayList(); - expectedIntents.addAll(generateVlanOneBrc()); - expectedIntents.addAll(generateVlanOneUni()); - expectedIntents.addAll(generateVlanTwoBrc()); - expectedIntents.addAll(generateVlanTwoUni()); + Set fcPoints; + Set hosts; + + fcPoints = buildFCPoints(ImmutableSet.of(v100h1, v200h1, v300h1)); + hosts = ImmutableSet.of(v100host1, v200host1, v300host1); + expectedIntents.addAll(generateVplsBrc(fcPoints, NET1)); + expectedIntents.addAll(generateVplsUni(fcPoints, hosts, NET1)); + + fcPoints = buildFCPoints(ImmutableSet.of(v100h2, v200h2, v300h2)); + hosts = ImmutableSet.of(v100host2, v200host2, v300host2); + expectedIntents.addAll(generateVplsBrc(fcPoints, NET2)); + expectedIntents.addAll(generateVplsUni(fcPoints, hosts, NET2)); checkIntents(expectedIntents); } /** - * Checks the case in which six ports are configured with VLANs and - * initially no hosts are registered by the HostService. The first three - * ports have an interface configured on VLAN1, the other three have an - * interface configured on VLAN2. When the module starts up, three hosts - - * on device one, two and three - port 1 (both on VLAN1), are registered by - * the HostService and events are sent to the application. sp2mp intents - * are created for all interfaces configured and mp2sp intents are created - * only for the hosts attached. - * The number of intents expected is nine: six for VLAN1, three for VLAN2. + * Six ports are configured with VLANs and initially no hosts are registered + * by the HostService. The first three ports have an interface configured on + * NET1, the other three have an interface configured on NET2. When the + * module starts up, three hosts attached to device one, two and three - + * port 1, are registered by the HostService and events are sent to the + * application. sp2mp intents are created for all interfaces configured and + * mp2sp intents are created only for the hosts attached. + * The number of intents expected is nine: six for NET1, three for NET2. * Six sp2mp intents, three mp2sp intents. IPs are added on the first two - * hosts only to demonstrate it doesn't influence the number of intents + * hosts only to demonstrate this doesn't influence the number of intents * created. - * An additional host is added on device seven, port one to demonstrate - * that, even if it's on the same VLAN of other interfaces configured in - * the system, it doesn't let the application generate intents, since it's - * not connected to the interface configured. + * An additional host is added on device seven - port 1, to demonstrate that + * the application does not generate intents, even if the interface uses the + * same VLAN Id of the other interfaces configured for the specifc VPLS. + * Checks if the number of intents submitted to the intent framework is equal + * to the number of intents expected and if all intents are equivalent. */ @Test - public void testFourInterfacesThreeHostEventsSameVlan() { + public void testSixInterfacesThreeHostEventsSameVpls() { vpls.activate(); - Host h1 = new DefaultHost(PID, HID1, MAC1, VLAN1, getLocation(1), - Collections.singleton(IP1)); - Host h2 = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(2), - Collections.singleton(IP2)); - Host h3 = new DefaultHost(PID, HID3, MAC3, VLAN1, getLocation(3), - Collections.EMPTY_SET); - Host h7 = new DefaultHost(PID, HID7, MAC7, VLAN1, getLocation(7), - Collections.EMPTY_SET); - hostsAvailable.addAll(Sets.newHashSet(h1, h2, h3, h7)); + List expectedIntents = Lists.newArrayList(); + Set fcPoints; + Set hosts; + + hostsAvailable.addAll(Sets.newHashSet(v100host1, v200host1, v300host1, v300host3)); hostsAvailable.forEach(host -> - hostListener.event(new HostEvent(HostEvent.Type.HOST_ADDED, host))); + hostListener.event(new HostEvent(HostEvent.Type.HOST_ADDED, host))); - List expectedIntents = Lists.newArrayList(); - expectedIntents.addAll(generateVlanOneBrc()); - expectedIntents.addAll(generateVlanOneUni()); - expectedIntents.addAll(generateVlanTwoBrc()); + fcPoints = buildFCPoints(ImmutableSet.of(v100h1, v200h1, v300h1)); + hosts = ImmutableSet.of(v100host1, v200host1, v300host1); + expectedIntents.addAll(generateVplsBrc(fcPoints, NET1)); + expectedIntents.addAll(generateVplsUni(fcPoints, hosts, NET1)); + + fcPoints = buildFCPoints(ImmutableSet.of(v100h2, v200h2, v300h2)); + expectedIntents.addAll(generateVplsBrc(fcPoints, NET2)); checkIntents(expectedIntents); } /** - * Checks the case in which six ports are configured with VLANs and - * initially no hosts are registered by the HostService. The first three - * ports have an interface configured on VLAN1, the other three have an - * interface configured on VLAN2. When the module starts up, two hosts - - * on device one and four - port 1 (VLAN 1 and VLAN 2), are registered by - * the HostService and events are sent to the application. sp2mp intents - * are created for all interfaces configured and no mp2sp intents are created - * at all, since the minimum number of hosts needed on the same vlan to - * create mp2sp intents is 2. - * The number of intents expected is six: three for VLAN1, three for VLAN2. - * six sp2mp intents, zero mp2sp intents. IPs are added on the first host - * only to demonstrate it doesn't influence the number of intents created. + * Generates a list of the expected sp2mp intents for a VPLS. + * + * @param fcPoints the filtered connect point + * @param name the name of the VPLS + * @return the list of expected sp2mp intents for the given VPLS */ - @Test - public void testFourInterfacesTwoHostEventsDifferentVlan() { - vpls.activate(); + private List + generateVplsBrc(Set fcPoints, String name) { + List intents = Lists.newArrayList(); - Host h1 = new DefaultHost(PID, HID1, MAC1, VLAN1, getLocation(1), - Collections.singleton(IP1)); - Host h4 = new DefaultHost(PID, HID4, MAC4, VLAN2, getLocation(4), - Collections.EMPTY_SET); - hostsAvailable.addAll(Sets.newHashSet(h1, h4)); + fcPoints.forEach(point -> { + Set otherPoints = + fcPoints.stream() + .filter(fcp -> !fcp.equals(point)) + .collect(Collectors.toSet()); - hostsAvailable.forEach(host -> { - hostListener.event(new HostEvent(HostEvent.Type.HOST_ADDED, host)); + Key brckey = buildKey(PREFIX_BROADCAST, + point.connectPoint(), name, MacAddress.BROADCAST); + + intents.add(buildBrcIntent(brckey, point, otherPoints)); }); - List expectedIntents = Lists.newArrayList(); - expectedIntents.addAll(generateVlanOneBrc()); - expectedIntents.addAll(generateVlanTwoBrc()); - - checkIntents(expectedIntents); + return intents; } /** - * Checks both that the number of intents in submitted in the intent - * framework it's equal to the number of intents expected and that all - * intents are equivalent. + * Generates a list of expected mp2sp intents for a given VPLS. + * + * @param fcPoints the filtered connect point + * @param hosts the hosts + * @param name the name of the VPLS + * @return the list of expected mp2sp intents for the given VPLS + */ + private List + generateVplsUni(Set fcPoints, Set hosts, String name) { + List intents = Lists.newArrayList(); + + hosts.forEach(host -> { + FilteredConnectPoint hostPoint = getHostPoint(host, fcPoints); + + Set otherPoints = + fcPoints.stream() + .filter(fcp -> !fcp.equals(hostPoint)) + .collect(Collectors.toSet()); + + Key uniKey = buildKey(PREFIX_UNICAST, + host.location(), name, host.mac()); + + intents.add(buildUniIntent(uniKey, otherPoints, hostPoint, host)); + }); + + return intents; + } + + /** + * Checks if the number of intents submitted to the intent framework is equal + * to the number of intents expected and if all intents are equivalent. * * @param intents the list of intents expected */ @@ -367,8 +450,8 @@ public class VplsTest { for (Intent intentTwo : intentService.getIntents()) { if (intentOne.key().equals(intentTwo.key())) { found = true; - assertTrue(format("Comparing %s and %s", intentOne, intentTwo), - IntentUtils.intentsAreEqual(intentOne, intentTwo)); + assertTrue(format(COMPARE, intentOne, intentTwo), + IntentUtils.intentsAreEqual(intentOne, intentTwo)); break; } } @@ -377,190 +460,143 @@ public class VplsTest { } /** - * Generates the list of the expected sp2mp intents for VLAN 1. + * Builds a broadcast intent. * - * @return the list of expected sp2mp intents for VLAN 1 - */ - private List generateVlanOneBrc() { - Key key = null; - - List intents = Lists.newArrayList(); - - // Building sp2mp intent for H1 - VLAN1 - key = Key.of((PREFIX_BROADCAST + "-" + DID1 + "-" + P1 + "-" + VLAN1), - APPID); - intents.add(buildBrcIntent(key, C1, Sets.newHashSet(C2, C3), VLAN1)); - - // Building sp2mp intent for H2 - VLAN1 - key = Key.of((PREFIX_BROADCAST + "-" + DID2 + "-" + P1 + "-" + VLAN1), - APPID); - intents.add(buildBrcIntent(key, C2, Sets.newHashSet(C1, C3), VLAN1)); - - // Building sp2mp intent for H3 - VLAN1 - key = Key.of((PREFIX_BROADCAST + "-" + DID3 + "-" + P1 + "-" + VLAN1), - APPID); - intents.add(buildBrcIntent(key, C3, Sets.newHashSet(C1, C2), VLAN1)); - - return intents; - } - - /** - * Generates the list of the expected mp2sp intents for VLAN 1. - * - * @return the list of expected mp2sp intents for VLAN 1 - */ - private List generateVlanOneUni() { - Key key = null; - - List intents = Lists.newArrayList(); - - // Building mp2sp intent for H1 - VLAN1 - key = Key.of((PREFIX_UNICAST + "-" + DID1 + "-" + P1 + "-" + VLAN1), - APPID); - intents.add(buildUniIntent(key, Sets.newHashSet(C2, C3), C1, VLAN1, MAC1)); - - // Building mp2sp intent for H2 - VLAN1 - key = Key.of((PREFIX_UNICAST + "-" + DID2 + "-" + P1 + "-" + VLAN1), - APPID); - intents.add(buildUniIntent(key, Sets.newHashSet(C1, C3), C2, VLAN1, MAC2)); - - // Building mp2sp intent for H3 - VLAN1 - key = Key.of((PREFIX_UNICAST + "-" + DID3 + "-" + P1 + "-" + VLAN1), - APPID); - intents.add(buildUniIntent(key, Sets.newHashSet(C1, C2), C3, VLAN1, MAC3)); - - return intents; - } - - /** - * Generates the list of the expected sp2mp intents for VLAN 2. - * - * @return the list of expected sp2mp intents for VLAN 2 - */ - private List generateVlanTwoBrc() { - Key key = null; - - List intents = Lists.newArrayList(); - - // Building sp2mp intent for H4 - VLAN2 - key = Key.of((PREFIX_BROADCAST + "-" + DID4 + "-" + P1 + "-" + VLAN2), - APPID); - intents.add(buildBrcIntent(key, C4, Sets.newHashSet(C5, C6), VLAN2)); - - // Building sp2mp intent for H5 - VLAN2 - key = Key.of((PREFIX_BROADCAST + "-" + DID5 + "-" + P1 + "-" + VLAN2), - APPID); - intents.add(buildBrcIntent(key, C5, Sets.newHashSet(C4, C6), VLAN2)); - - // Building sp2mp intent for H6 - VLAN2 - key = Key.of((PREFIX_BROADCAST + "-" + DID6 + "-" + P1 + "-" + VLAN2), - APPID); - intents.add(buildBrcIntent(key, C6, Sets.newHashSet(C4, C5), VLAN2)); - - return intents; - } - - /** - * Generates the list of the expected mp2sp intents for VLAN 2. - * - * @return the list of expected mp2sp intents for VLAN 2 - */ - private List generateVlanTwoUni() { - Key key = null; - - List intents = Lists.newArrayList(); - - // Building mp2sp intent for H4 - VLAN2 - key = Key.of((PREFIX_UNICAST + "-" + DID4 + "-" + P1 + "-" + VLAN2), - APPID); - intents.add(buildUniIntent(key, Sets.newHashSet(C5, C6), C4, VLAN2, MAC4)); - - // Building mp2sp intent for H5 - VLAN2 - key = Key.of((PREFIX_UNICAST + "-" + DID5 + "-" + P1 + "-" + VLAN2), - APPID); - intents.add(buildUniIntent(key, Sets.newHashSet(C4, C6), C5, VLAN2, MAC5)); - - // Building mp2sp intent for H6 - VLAN2 - key = Key.of((PREFIX_UNICAST + "-" + DID6 + "-" + P1 + "-" + VLAN2), - APPID); - intents.add(buildUniIntent(key, Sets.newHashSet(C4, C5), C6, VLAN2, MAC6)); - - return intents; - } - - /** - * Builds a Single Point to Multi Point intent. - * - * @param key The intent key - * @param src The source Connect Point - * @param dsts The destination Connect Points - * @return Single Point to Multi Point intent generated. + * @param key the key to identify the intent + * @param src the ingress connect point + * @param dsts the egress connect points + * @return the generated single-point to multi-point intent */ private SinglePointToMultiPointIntent buildBrcIntent(Key key, - ConnectPoint src, - Set dsts, - VlanId vlanId) { + FilteredConnectPoint src, + Set dsts) { SinglePointToMultiPointIntent intent; - TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); - TrafficSelector selector = DefaultTrafficSelector.builder() .matchEthDst(MacAddress.BROADCAST) - .matchVlanId(vlanId) .build(); intent = SinglePointToMultiPointIntent.builder() .appId(APPID) .key(key) .selector(selector) - .treatment(treatment) - .ingressPoint(src) - .egressPoints(dsts) + .filteredIngressPoint(src) + .filteredEgressPoints(dsts) .priority(PRIORITY_OFFSET) .build(); return intent; } /** - * Builds a Multi Point to Single Point intent. + * Builds a unicast intent. * - * @param key The intent key - * @param srcs The source Connect Points - * @param dst The destination Connect Point - * @return Multi Point to Single Point intent generated. + * @param key the key to identify the intent + * @param srcs the ingress connect points + * @param dst the egress connect point + * @param host the destination Host + * @return the generated multi-point to single-point intent */ private MultiPointToSinglePointIntent buildUniIntent(Key key, - Set srcs, - ConnectPoint dst, - VlanId vlanId, - MacAddress mac) { - MultiPointToSinglePointIntent intent; + Set srcs, + FilteredConnectPoint dst, + Host host) { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthDst(host.mac()) + .build(); - TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); - - TrafficSelector.Builder builder = DefaultTrafficSelector.builder() - .matchEthDst(mac) - .matchVlanId(vlanId); - - TrafficSelector selector = builder.build(); - - intent = MultiPointToSinglePointIntent.builder() + return MultiPointToSinglePointIntent.builder() .appId(APPID) .key(key) .selector(selector) - .treatment(treatment) - .ingressPoints(srcs) - .egressPoint(dst) + .filteredIngressPoints(srcs) + .filteredEgressPoint(dst) .priority(PRIORITY_OFFSET) .build(); - return intent; + } /** - * Returns the device ID of the ith device. + * Returns the filtered connect point associated to a given host. * - * @param i device to get the ID of - * @return the device ID + * @param host the target host + * @param fcps the filtered connected points + * @return the filtered connect point associated to the given host; null + * otherwise + */ + private FilteredConnectPoint getHostPoint(Host host, + Set 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 given interfaces. + * + * @param interfaces the interfaces belonging to the VPLS + * @return the set of filtered connect points + */ + private Set buildFCPoints(Set interfaces) { + // Build all filtered connected point 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()); + } + + /** + * Builds an intent Key either for a single-point to multi-point or + * multi-point to single-point intent, based on a prefix that defines + * the intent type, the connection point representing the source or the + * destination and the VLAN Id representing the network. + * + * @param prefix the key prefix + * @param cPoint the ingress/egress connect point + * @param networkName the VPLS name + * @param hostMac the ingress/egress MAC address + * @return the key to identify the intent + */ + private Key buildKey(String prefix, + ConnectPoint cPoint, + String networkName, + MacAddress hostMac) { + String keyString = networkName + + DASH + + prefix + + DASH + + cPoint.deviceId() + + DASH + + cPoint.port() + + DASH + + hostMac; + + return Key.of(keyString, APPID); + } + + /** + * Returns the device Id of the ith device. + * + * @param i the device to get the Id of + * @return the device Id */ private static DeviceId getDeviceId(int i) { return DeviceId.deviceId("" + i); @@ -571,8 +607,8 @@ public class VplsTest { } /** - * Represents a fake IntentService class that easily allows to store and - * retrieve intents without implementing the IntentService logic. + * Represents a fake IntentService class that allows to store and retrieve + * intents without implementing the IntentService logic. */ private class TestIntentService extends IntentServiceAdapter { @@ -628,13 +664,15 @@ public class VplsTest { @Override public Set getConnectedHosts(ConnectPoint connectPoint) { return hosts.stream() - .filter(h -> h.location().elementId().equals(connectPoint.elementId()) - && h.location().port().equals(connectPoint.port())) + .filter(h -> h.location().equals(connectPoint)) .collect(Collectors.toSet()); } } + /** + * Represents a fake IdGenerator class for intents. + */ private static class TestIdGenerator implements IdGenerator { private final AtomicLong id = new AtomicLong(0); @@ -656,7 +694,7 @@ public class VplsTest { private final IntentService intentService; /** - * Creates a new test intent synchronizer. + * Creates a new intent test synchronizer. * * @param intentService intent service */ @@ -687,4 +725,120 @@ public class VplsTest { } } + /** + * Represents a fake VplsConfigService class which is needed for testing. + */ + private class TestVplsConfigService implements VplsConfigurationService { + + private final SetMultimap vplsNetworks; + private Set vplsAffectByApi = new HashSet<>(); + + TestVplsConfigService(SetMultimap vplsNetworks) { + this.vplsNetworks = vplsNetworks; + } + + @Override + public void addVpls(String name, Set ifaces) { + if (!vplsNetworks.containsKey(name)) { + ifaces.forEach(iface -> { + avaliableInterfaces.forEach(intf -> { + if (intf.name().equals(iface)) { + vplsNetworks.put(name, intf); + } + }); + }); + } + } + + @Override + public void removeVpls(String name) { + if (vplsNetworks.containsKey(name)) { + vplsNetworks.removeAll(name); + } + } + + @Override + public void addInterfaceToVpls(String name, String iface) { + if (!vplsNetworks.containsKey(name)) { + avaliableInterfaces.forEach(intf -> { + if (intf.name().equals(iface)) { + vplsNetworks.put(name, intf); + } + }); + } + } + + @Override + public void removeInterfaceFromVpls(String iface) { + SetMultimap search = HashMultimap.create(vplsNetworks); + search.entries().forEach(e -> { + if (e.getValue().name().equals(iface)) { + vplsNetworks.remove(e.getKey(), iface); + } + }); + } + + @Override + public void cleanVpls() { + vplsNetworks.clear(); + } + + @Override + public Set getVplsAffectedByApi() { + Set vplsNames = ImmutableSet.copyOf(vplsAffectByApi); + + vplsAffectByApi.clear(); + + return vplsNames; + } + + @Override + public Set getAllInterfaces() { + return vplsNetworks.values() + .stream() + .collect(Collectors.toSet()); + } + + @Override + public Set getVplsInterfaces(String name) { + return vplsNetworks.get(name) + .stream() + .collect(Collectors.toSet()); + } + + @Override + public Set getAllVpls() { + return vplsNetworks.keySet(); + } + + @Override + public Set getOldVpls() { + return vplsNetworks.keySet(); + } + + @Override + public SetMultimap getVplsNetworks() { + return ImmutableSetMultimap.copyOf(vplsNetworks); + } + + @Override + public SetMultimap getVplsNetwork(VlanId vlan, + ConnectPoint connectPoint) { + String vplsNetworkName = + vplsNetworks.entries().stream() + .filter(e -> e.getValue().connectPoint().equals(connectPoint)) + .filter(e -> e.getValue().vlan().equals(vlan)) + .map(e -> e.getKey()) + .findFirst() + .orElse(null); + SetMultimap result = HashMultimap.create(); + if (vplsNetworkName != null && vplsNetworks.containsKey(vplsNetworkName)) { + vplsNetworks.get(vplsNetworkName) + .forEach(intf -> result.put(vplsNetworkName, intf)); + return result; + } + return null; + } + + } } diff --git a/apps/vpls/src/test/java/org/onosproject/vpls/config/VplsConfigTest.java b/apps/vpls/src/test/java/org/onosproject/vpls/config/VplsConfigTest.java new file mode 100644 index 0000000000..6ef028427d --- /dev/null +++ b/apps/vpls/src/test/java/org/onosproject/vpls/config/VplsConfigTest.java @@ -0,0 +1,236 @@ +/* + * 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.config; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigApplyDelegate; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for the {@link VplsConfig} class. + */ +public class VplsConfigTest { + private static final String APP_NAME = "org.onosproject.vpls"; + private static final ApplicationId APP_ID = new TestApplicationId(APP_NAME); + private static final String VPLS = "vplsNetworks"; + private static final String NAME = "name"; + private static final String INTERFACE = "interfaces"; + private static final String NET1 = "net1"; + private static final String NET2 = "net2"; + private static final String NEWNET = "newnet"; + + private static final String IF1 = "sw5-4-100"; + private static final String IF2 = "sw5-4-200"; + private static final String IF3 = "sw5-5-100"; + private static final String IF4 = "sw6-5-100"; + private static final String IF5 = "sw6-5-400"; + private static final String IF_NON_EXIST = "sw7-5-100"; + + private static final String JSON_TREE = "{\"" + VPLS + + "\" : [{\"" + NAME + "\" : \"net1\"," + + "\"" + INTERFACE + "\" : [" + + "\"sw5-4-100\",\"sw5-4-200\",\"sw5-5-100\"]}]}"; + private static final String EMPTY_JSON_TREE = "{}"; + + private final ObjectMapper mapper = new ObjectMapper(); + private final ConfigApplyDelegate delegate = new MockCfgDelegate(); + private final VplsNetworkConfig initialNetwork = createInitialNetwork(); + + private Set networks = new HashSet<>(); + private VplsConfig vplsConfig = new VplsConfig(); + private VplsConfig emptyVplsConfig = new VplsConfig(); + + @Before + public void setUp() throws Exception { + JsonNode tree = new ObjectMapper().readTree(JSON_TREE); + vplsConfig.init(APP_ID, APP_NAME, tree, mapper, delegate); + JsonNode emptyTree = new ObjectMapper().readTree(EMPTY_JSON_TREE); + emptyVplsConfig.init(APP_ID, APP_NAME, emptyTree, mapper, delegate); + networks.add(initialNetwork); + } + + /** + * Tests if a VPLS configuration can be retrieved from JSON. + */ + @Test + public void testVplsNetworks() { + assertEquals(networks, vplsConfig.vplsNetworks()); + } + + /** + * Tests an empty VPLS application configuration is retrieved from JSON. + */ + @Test + public void testEmptyVplsNetworks() { + assertTrue(emptyVplsConfig.vplsNetworks().isEmpty()); + } + + /** + * Tests if a VPLS can be found by name. + */ + @Test + public void testGetVplsWithName() { + assertNotNull(vplsConfig.getVplsWithName(NET1)); + assertNull(vplsConfig.getVplsWithName(NET2)); + } + + /** + * Tests the addition of a new VPLS. + */ + @Test + public void testAddNetwork() { + int initialSize = vplsConfig.vplsNetworks().size(); + VplsNetworkConfig newNetwork = createNewNetwork(); + vplsConfig.addVpls(newNetwork); + assertEquals(initialSize + 1, vplsConfig.vplsNetworks().size()); + networks.add(newNetwork); + assertEquals(networks, vplsConfig.vplsNetworks()); + } + + /** + * Tests the addition of new VPLS to an empty configuration. + */ + @Test + public void testAddNetworkToEmpty() { + VplsNetworkConfig newNetwork = createNewNetwork(); + emptyVplsConfig.addVpls(newNetwork); + + assertFalse(emptyVplsConfig.vplsNetworks().isEmpty()); + } + + /** + * Tests the removal of an existing VPLS from the configuration. + */ + @Test + public void testRemoveExistingNetwork() { + int initialSize = vplsConfig.vplsNetworks().size(); + vplsConfig.removeVpls(NET1); + + assertEquals(initialSize - 1, vplsConfig.vplsNetworks().size()); + } + + /** + * Tests the removal of a non-existing VPLS from the configuration. + */ + @Test + public void testRemoveInexistingNetwork() { + int initialSize = vplsConfig.vplsNetworks().size(); + vplsConfig.removeVpls(NET2); + + assertEquals(initialSize, vplsConfig.vplsNetworks().size()); + } + + /** + * Tests the addition of a new interface. + */ + @Test + public void testAddInterfaceToNetwork() { + int initialSize = vplsConfig.getVplsWithName(NET1).ifaces().size(); + vplsConfig.addInterfaceToVpls(NET1, IF4); + + assertEquals(initialSize + 1, vplsConfig.getVplsWithName(NET1).ifaces().size()); + } + + /** + * Tests the addition of a new interface when it already exists. + */ + @Test + public void testAddExistingInterfaceToNetwork() { + int initialSize = vplsConfig.getVplsWithName(NET1).ifaces().size(); + vplsConfig.addInterfaceToVpls(NET1, IF1); + + assertEquals(initialSize, vplsConfig.getVplsWithName(NET1).ifaces().size()); + } + + /** + * Tests the retrieval of a VPLS, given an interface name. + */ + @Test + public void testgetNetworkFromInterface() { + assertNotNull(vplsConfig.getVplsFromInterface(IF1)); + assertNull(vplsConfig.getVplsFromInterface(IF_NON_EXIST)); + } + + /** + * Tests the removal of an interface. + */ + @Test + public void testRemoveExistingInterfaceFromNetwork() { + int initialSize = vplsConfig.getVplsWithName(NET1).ifaces().size(); + vplsConfig.removeInterfaceFromVpls(initialNetwork, IF1); + + assertEquals(initialSize - 1, vplsConfig.getVplsWithName(NET1).ifaces().size()); + } + + /** + * Tests the removal of an interface from a VPLS when it does not exist. + */ + @Test + public void testRemoveNonExistingInterfaceFromNetwork() { + int initialSize = vplsConfig.getVplsWithName(NET1).ifaces().size(); + vplsConfig.removeInterfaceFromVpls(initialNetwork, IF_NON_EXIST); + + assertEquals(initialSize, vplsConfig.getVplsWithName(NET1).ifaces().size()); + } + + /** + * Tests if the two interfaces are attached to the network + * while one of the interface is attached and another one is not. + */ + @Test + public void testIsAttached() { + VplsNetworkConfig network = createNewNetwork(); + + assertTrue(network.isAttached(IF4)); + assertFalse(network.isAttached(IF_NON_EXIST)); + } + + private class MockCfgDelegate implements ConfigApplyDelegate { + + @Override + public void onApply(@SuppressWarnings("rawtypes") Config config) { + config.apply(); + } + + } + + private VplsNetworkConfig createInitialNetwork() { + Set ifaces = new HashSet<>(Arrays.asList(IF1, IF2, IF3)); + + return new VplsNetworkConfig(NET1, ifaces); + } + + private VplsNetworkConfig createNewNetwork() { + Set ifaces = new HashSet<>(Arrays.asList(IF4, IF5)); + + return new VplsNetworkConfig(NEWNET, ifaces); + } +}