diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/core/net/src | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/core/net/src')
148 files changed, 25446 insertions, 0 deletions
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java new file mode 100644 index 00000000..161659f9 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java @@ -0,0 +1,250 @@ +/* + * Copyright 2015 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.app.impl; + +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.apache.karaf.features.Feature; +import org.apache.karaf.features.FeaturesService; +import org.onosproject.app.ApplicationAdminService; +import org.onosproject.app.ApplicationEvent; +import org.onosproject.app.ApplicationListener; +import org.onosproject.app.ApplicationService; +import org.onosproject.app.ApplicationState; +import org.onosproject.app.ApplicationStore; +import org.onosproject.app.ApplicationStoreDelegate; +import org.onosproject.event.AbstractListenerManager; +import org.onosproject.core.Application; +import org.onosproject.core.ApplicationId; +import org.onosproject.security.Permission; +import org.onosproject.security.SecurityUtil; +import org.slf4j.Logger; + +import java.io.InputStream; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.app.ApplicationEvent.Type.*; +import static org.onosproject.security.AppPermission.Type.*; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Implementation of the application management service. + */ +@Component(immediate = true) +@Service +public class ApplicationManager + extends AbstractListenerManager<ApplicationEvent, ApplicationListener> + implements ApplicationService, ApplicationAdminService { + + private final Logger log = getLogger(getClass()); + + private static final String APP_ID_NULL = "Application ID cannot be null"; + + private final ApplicationStoreDelegate delegate = new InternalStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ApplicationStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FeaturesService featuresService; + + private boolean initializing; + + @Activate + public void activate() { + eventDispatcher.addSink(ApplicationEvent.class, listenerRegistry); + + initializing = true; + store.setDelegate(delegate); + initializing = false; + + log.info("Started"); + } + + @Deactivate + public void deactivate() { + eventDispatcher.removeSink(ApplicationEvent.class); + store.unsetDelegate(delegate); + log.info("Stopped"); + } + + @Override + public Set<Application> getApplications() { + checkPermission(APP_READ); + return store.getApplications(); + } + + @Override + public ApplicationId getId(String name) { + checkPermission(APP_READ); + checkNotNull(name, "Name cannot be null"); + return store.getId(name); + } + + @Override + public Application getApplication(ApplicationId appId) { + checkPermission(APP_READ); + checkNotNull(appId, APP_ID_NULL); + return store.getApplication(appId); + } + + @Override + public ApplicationState getState(ApplicationId appId) { + checkPermission(APP_READ); + checkNotNull(appId, APP_ID_NULL); + return store.getState(appId); + } + + @Override + public Set<Permission> getPermissions(ApplicationId appId) { + checkPermission(APP_READ); + checkNotNull(appId, APP_ID_NULL); + return store.getPermissions(appId); + } + + @Override + public Application install(InputStream appDescStream) { + checkNotNull(appDescStream, "Application archive stream cannot be null"); + Application app = store.create(appDescStream); + SecurityUtil.register(app.id()); + return app; + } + + @Override + public void uninstall(ApplicationId appId) { + checkNotNull(appId, APP_ID_NULL); + try { + store.remove(appId); + } catch (Exception e) { + log.warn("Unable to purge application directory for {}", appId.name()); + } + } + + @Override + public void activate(ApplicationId appId) { + checkNotNull(appId, APP_ID_NULL); + if (!SecurityUtil.isAppSecured(appId)) { + return; + } + store.activate(appId); + } + + @Override + public void deactivate(ApplicationId appId) { + checkNotNull(appId, APP_ID_NULL); + store.deactivate(appId); + } + + @Override + public void setPermissions(ApplicationId appId, Set<Permission> permissions) { + checkNotNull(appId, APP_ID_NULL); + checkNotNull(permissions, "Permissions cannot be null"); + store.setPermissions(appId, permissions); + } + + private class InternalStoreDelegate implements ApplicationStoreDelegate { + @Override + public void notify(ApplicationEvent event) { + ApplicationEvent.Type type = event.type(); + Application app = event.subject(); + try { + if (type == APP_ACTIVATED) { + if (installAppFeatures(app)) { + log.info("Application {} has been activated", app.id().name()); + } + + } else if (type == APP_DEACTIVATED) { + if (uninstallAppFeatures(app)) { + log.info("Application {} has been deactivated", app.id().name()); + } + + } else if (type == APP_INSTALLED) { + if (installAppArtifacts(app)) { + log.info("Application {} has been installed", app.id().name()); + } + + } else if (type == APP_UNINSTALLED) { + if (uninstallAppFeatures(app) || uninstallAppArtifacts(app)) { + log.info("Application {} has been uninstalled", app.id().name()); + } + + } + post(event); + + } catch (Exception e) { + log.warn("Unable to perform operation on application " + app.id().name(), e); + } + } + } + + // The following methods are fully synchronized to guard against remote vs. + // locally induced feature service interactions. + + private synchronized boolean installAppArtifacts(Application app) throws Exception { + if (app.featuresRepo().isPresent() && + featuresService.getRepository(app.featuresRepo().get()) == null) { + featuresService.addRepository(app.featuresRepo().get()); + return true; + } + return false; + } + + private synchronized boolean uninstallAppArtifacts(Application app) throws Exception { + if (app.featuresRepo().isPresent() && + featuresService.getRepository(app.featuresRepo().get()) != null) { + featuresService.removeRepository(app.featuresRepo().get()); + return true; + } + return false; + } + + private synchronized boolean installAppFeatures(Application app) throws Exception { + boolean changed = false; + for (String name : app.features()) { + Feature feature = featuresService.getFeature(name); + if (feature != null && !featuresService.isInstalled(feature)) { + featuresService.installFeature(name); + changed = true; + } else if (feature == null && !initializing) { + // Suppress feature-not-found reporting during startup since these + // can arise naturally from the staggered cluster install. + log.warn("Feature {} not found", name); + } + } + return changed; + } + + private synchronized boolean uninstallAppFeatures(Application app) throws Exception { + boolean changed = false; + for (String name : app.features()) { + Feature feature = featuresService.getFeature(name); + if (feature != null && featuresService.isInstalled(feature)) { + featuresService.uninstallFeature(name); + changed = true; + } else if (feature == null) { + log.warn("Feature {} not found", name); + } + } + return changed; + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java new file mode 100644 index 00000000..d5f30374 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Subsystem for managing applications. + */ +package org.onosproject.app.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java new file mode 100644 index 00000000..1933ee55 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java @@ -0,0 +1,281 @@ +/* + * Copyright 2015 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.cfg.impl; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +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.util.AbstractAccumulator; +import org.onlab.util.Accumulator; +import org.onlab.util.SharedExecutors; +import org.onosproject.cfg.ComponentConfigEvent; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cfg.ComponentConfigStore; +import org.onosproject.cfg.ComponentConfigStoreDelegate; +import org.onosproject.cfg.ConfigProperty; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Implementation of the centralized component configuration service. + */ +@Component(immediate = true) +@Service +public class ComponentConfigManager implements ComponentConfigService { + + private static final String COMPONENT_NULL = "Component name cannot be null"; + private static final String PROPERTY_NULL = "Property name cannot be null"; + + //Symbolic constants for use with the accumulator + private static final int MAX_ITEMS = 100; + private static final int MAX_BATCH_MILLIS = 1000; + private static final int MAX_IDLE_MILLIS = 250; + + private static final String RESOURCE_EXT = ".cfgdef"; + + private final Logger log = getLogger(getClass()); + + private final ComponentConfigStoreDelegate delegate = new InternalStoreDelegate(); + private final InternalAccumulator accumulator = new InternalAccumulator(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ConfigurationAdmin cfgAdmin; + + // Locally maintained catalog of definitions. + private final Map<String, Map<String, ConfigProperty>> properties = + Maps.newConcurrentMap(); + + + @Activate + public void activate() { + store.setDelegate(delegate); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + log.info("Stopped"); + } + + @Override + public Set<String> getComponentNames() { + checkPermission(CONFIG_READ); + + return ImmutableSet.copyOf(properties.keySet()); + } + + @Override + public void registerProperties(Class<?> componentClass) { + checkPermission(CONFIG_WRITE); + + String componentName = componentClass.getName(); + String resourceName = componentClass.getSimpleName() + RESOURCE_EXT; + try (InputStream ris = componentClass.getResourceAsStream(resourceName)) { + checkArgument(ris != null, "Property definitions not found at resource %s", + resourceName); + + // Read the definitions + Set<ConfigProperty> defs = ConfigPropertyDefinitions.read(ris); + + // Produce a new map of the properties and register it. + Map<String, ConfigProperty> map = Maps.newConcurrentMap(); + defs.forEach(p -> map.put(p.name(), p)); + + properties.put(componentName, map); + loadExistingValues(componentName); + } catch (IOException e) { + log.error("Unable to read property definitions from resource " + resourceName, e); + } + } + + @Override + public void unregisterProperties(Class<?> componentClass, boolean clear) { + checkPermission(CONFIG_WRITE); + + String componentName = componentClass.getName(); + checkNotNull(componentName, COMPONENT_NULL); + Map<String, ConfigProperty> cps = properties.remove(componentName); + if (clear && cps != null) { + cps.keySet().forEach(name -> store.unsetProperty(componentName, name)); + clearExistingValues(componentName); + } + } + + // Clears any existing values that may have been set. + private void clearExistingValues(String componentName) { + triggerUpdate(componentName); + } + + @Override + public Set<ConfigProperty> getProperties(String componentName) { + checkPermission(CONFIG_READ); + + Map<String, ConfigProperty> map = properties.get(componentName); + return map != null ? ImmutableSet.copyOf(map.values()) : null; + } + + @Override + public void setProperty(String componentName, String name, String value) { + checkPermission(CONFIG_WRITE); + + checkNotNull(componentName, COMPONENT_NULL); + checkNotNull(name, PROPERTY_NULL); + store.setProperty(componentName, name, value); + } + + @Override + public void unsetProperty(String componentName, String name) { + checkPermission(CONFIG_WRITE); + + checkNotNull(componentName, COMPONENT_NULL); + checkNotNull(name, PROPERTY_NULL); + store.unsetProperty(componentName, name); + } + + private class InternalStoreDelegate implements ComponentConfigStoreDelegate { + + @Override + public void notify(ComponentConfigEvent event) { + String componentName = event.subject(); + String name = event.name(); + String value = event.value(); + + switch (event.type()) { + case PROPERTY_SET: + set(componentName, name, value); + break; + case PROPERTY_UNSET: + reset(componentName, name); + break; + default: + break; + } + } + } + + // Buffers multiple subsequent configuration updates into one notification. + private class InternalAccumulator extends AbstractAccumulator<String> + implements Accumulator<String> { + + protected InternalAccumulator() { + super(SharedExecutors.getTimer(), MAX_ITEMS, MAX_BATCH_MILLIS, MAX_IDLE_MILLIS); + } + + @Override + public void processItems(List<String> items) { + // Conversion to set removes duplicates + Set<String> componentSet = new HashSet<>(items); + componentSet.forEach(ComponentConfigManager.this::triggerUpdate); + } + } + + // Locates the property in the component map and replaces it with an + // updated copy. + private void set(String componentName, String name, String value) { + Map<String, ConfigProperty> map = properties.get(componentName); + if (map != null) { + ConfigProperty prop = map.get(name); + if (prop != null) { + map.put(name, ConfigProperty.setProperty(prop, value)); + accumulator.add(componentName); + return; + } + } + log.warn("Unable to set non-existent property {} for component {}", + name, componentName); + } + + // Locates the property in the component map and replaces it with an + // reset copy. + private void reset(String componentName, String name) { + Map<String, ConfigProperty> map = properties.get(componentName); + if (map != null) { + ConfigProperty prop = map.get(name); + if (prop != null) { + map.put(name, ConfigProperty.resetProperty(prop)); + accumulator.add(componentName); + return; + } + log.warn("Unable to reset non-existent property {} for component {}", + name, componentName); + } + } + + // Loads existing property values that may have been set. + private void loadExistingValues(String componentName) { + try { + Configuration cfg = cfgAdmin.getConfiguration(componentName, null); + Map<String, ConfigProperty> map = properties.get(componentName); + Dictionary<String, Object> props = cfg.getProperties(); + if (props != null) { + Enumeration<String> it = props.keys(); + while (it.hasMoreElements()) { + String name = it.nextElement(); + ConfigProperty p = map.get(name); + if (p != null) { + map.put(name, ConfigProperty.setProperty(p, (String) props.get(name))); + } + } + } + } catch (IOException e) { + log.error("Unable to get configuration for " + componentName, e); + } + + } + + // FIXME: This should be a slightly deferred execution to allow changing + // values just once per component when a number of updates arrive shortly + // after each other. + private void triggerUpdate(String componentName) { + try { + Configuration cfg = cfgAdmin.getConfiguration(componentName, null); + Map<String, ConfigProperty> map = properties.get(componentName); + Dictionary<String, Object> props = new Hashtable<>(); + map.values().forEach(p -> props.put(p.name(), p.value())); + cfg.update(props); + } catch (IOException e) { + log.warn("Unable to update configuration for " + componentName, e); + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java new file mode 100644 index 00000000..0f416c76 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 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.cfg.impl; + +import com.google.common.collect.ImmutableSet; +import org.onosproject.cfg.ConfigProperty; +import org.onosproject.cfg.ConfigProperty.Type; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.Set; + +import static org.onosproject.cfg.ConfigProperty.defineProperty; + +/** + * Utility for writing and reading configuration property definition file. + */ +public final class ConfigPropertyDefinitions { + + private static final String FMT = "%s|%s|%s|%s\n"; + private static final String SEP = "\\|"; + private static final String COMMENT = "#"; + + private ConfigPropertyDefinitions() { + } + + /** + * Writes the specified set of property definitions into the given output + * stream. + * + * @param stream output stream + * @param props properties whose definitions are to be written + * @throws java.io.IOException if unable to write the stream + */ + public static void write(OutputStream stream, Set<ConfigProperty> props) throws IOException { + try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(stream))) { + props.forEach(p -> pw.format(FMT, p.name(), p.type(), p.description(), p.defaultValue())); + } + } + + /** + * Reads the specified input stream and creates from its contents a + * set of property definitions. + * + * @param stream input stream + * @return properties whose definitions are contained in the stream + * @throws java.io.IOException if unable to read the stream + */ + public static Set<ConfigProperty> read(InputStream stream) throws IOException { + ImmutableSet.Builder<ConfigProperty> builder = ImmutableSet.builder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) { + String line; + while ((line = br.readLine()) != null) { + if (!line.isEmpty() && !line.startsWith(COMMENT)) { + String[] f = line.split(SEP, 4); + builder.add(defineProperty(f[0], Type.valueOf(f[1]), f[2], f[3])); + } + } + } + return builder.build(); + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java new file mode 100644 index 00000000..4f76c31c --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Subsystem for central management of component configurations. + */ +package org.onosproject.cfg.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java new file mode 100644 index 00000000..04d1dfdf --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java @@ -0,0 +1,156 @@ +/* + * Copyright 2014-2015 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.cluster.impl; + +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.apache.karaf.system.SystemService; +import org.joda.time.DateTime; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterAdminService; +import org.onosproject.cluster.ClusterDefinitionService; +import org.onosproject.cluster.ClusterEvent; +import org.onosproject.cluster.ClusterEventListener; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ClusterStore; +import org.onosproject.cluster.ClusterStoreDelegate; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.event.AbstractListenerManager; +import org.slf4j.Logger; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + + +/** + * Implementation of the cluster service. + */ +@Component(immediate = true) +@Service +public class ClusterManager + extends AbstractListenerManager<ClusterEvent, ClusterEventListener> + implements ClusterService, ClusterAdminService { + + public static final String INSTANCE_ID_NULL = "Instance ID cannot be null"; + private final Logger log = getLogger(getClass()); + + private ClusterStoreDelegate delegate = new InternalStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterDefinitionService clusterDefinitionService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected SystemService systemService; + + @Activate + public void activate() { + store.setDelegate(delegate); + eventDispatcher.addSink(ClusterEvent.class, listenerRegistry); + clusterDefinitionService.seedNodes() + .forEach(node -> store.addNode(node.id(), node.ip(), node.tcpPort())); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + eventDispatcher.removeSink(ClusterEvent.class); + log.info("Stopped"); + } + + @Override + public ControllerNode getLocalNode() { + checkPermission(CLUSTER_READ); + return store.getLocalNode(); + } + + @Override + public Set<ControllerNode> getNodes() { + checkPermission(CLUSTER_READ); + return store.getNodes(); + } + + @Override + public ControllerNode getNode(NodeId nodeId) { + checkPermission(CLUSTER_READ); + checkNotNull(nodeId, INSTANCE_ID_NULL); + return store.getNode(nodeId); + } + + @Override + public ControllerNode.State getState(NodeId nodeId) { + checkPermission(CLUSTER_READ); + checkNotNull(nodeId, INSTANCE_ID_NULL); + return store.getState(nodeId); + } + + + @Override + public DateTime getLastUpdated(NodeId nodeId) { + checkPermission(CLUSTER_READ); + return store.getLastUpdated(nodeId); + } + + @Override + public void formCluster(Set<ControllerNode> nodes, String ipPrefix) { + checkNotNull(nodes, "Nodes cannot be null"); + checkArgument(!nodes.isEmpty(), "Nodes cannot be empty"); + checkNotNull(ipPrefix, "IP prefix cannot be null"); + clusterDefinitionService.formCluster(nodes, ipPrefix); + try { + log.warn("Shutting down container for cluster reconfiguration!"); + systemService.reboot("now", SystemService.Swipe.NONE); + } catch (Exception e) { + log.error("Unable to reboot container", e); + } + } + + @Override + public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) { + checkNotNull(nodeId, INSTANCE_ID_NULL); + checkNotNull(ip, "IP address cannot be null"); + checkArgument(tcpPort > 5000, "TCP port must be > 5000"); + return store.addNode(nodeId, ip, tcpPort); + } + + @Override + public void removeNode(NodeId nodeId) { + checkNotNull(nodeId, INSTANCE_ID_NULL); + store.removeNode(nodeId); + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements ClusterStoreDelegate { + @Override + public void notify(ClusterEvent event) { + post(event); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java new file mode 100644 index 00000000..56d369fd --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java @@ -0,0 +1,282 @@ +/* + * Copyright 2014-2015 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.cluster.impl; + +import com.codahale.metrics.Timer; +import com.codahale.metrics.Timer.Context; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +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.metrics.MetricsService; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.cluster.RoleInfo; +import org.onosproject.event.AbstractListenerManager; +import org.onosproject.core.MetricsHelper; +import org.onosproject.mastership.MastershipAdminService; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.mastership.MastershipStore; +import org.onosproject.mastership.MastershipStoreDelegate; +import org.onosproject.mastership.MastershipTerm; +import org.onosproject.mastership.MastershipTermService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.newArrayList; +import static org.onlab.metrics.MetricsUtil.startTimer; +import static org.onlab.metrics.MetricsUtil.stopTimer; +import static org.onosproject.cluster.ControllerNode.State.ACTIVE; +import static org.onosproject.net.MastershipRole.MASTER; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + + +@Component(immediate = true) +@Service +public class MastershipManager + extends AbstractListenerManager<MastershipEvent, MastershipListener> + implements MastershipService, MastershipAdminService, MastershipTermService, + MetricsHelper { + + private static final String NODE_ID_NULL = "Node ID cannot be null"; + private static final String DEVICE_ID_NULL = "Device ID cannot be null"; + private static final String ROLE_NULL = "Mastership role cannot be null"; + + private final Logger log = getLogger(getClass()); + + private final MastershipStoreDelegate delegate = new InternalDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MetricsService metricsService; + + private NodeId localNodeId; + private Timer requestRoleTimer; + + @Activate + public void activate() { + requestRoleTimer = createTimer("Mastership", "requestRole", "responseTime"); + localNodeId = clusterService.getLocalNode().id(); + eventDispatcher.addSink(MastershipEvent.class, listenerRegistry); + store.setDelegate(delegate); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + eventDispatcher.removeSink(MastershipEvent.class); + store.unsetDelegate(delegate); + log.info("Stopped"); + } + + @Override + public CompletableFuture<Void> setRole(NodeId nodeId, DeviceId deviceId, MastershipRole role) { + checkNotNull(nodeId, NODE_ID_NULL); + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(role, ROLE_NULL); + + CompletableFuture<MastershipEvent> eventFuture = null; + + switch (role) { + case MASTER: + eventFuture = store.setMaster(nodeId, deviceId); + break; + case STANDBY: + eventFuture = store.setStandby(nodeId, deviceId); + break; + case NONE: + eventFuture = store.relinquishRole(nodeId, deviceId); + break; + default: + log.info("Unknown role; ignoring"); + return CompletableFuture.completedFuture(null); + } + + return eventFuture.thenAccept(this::post) + .thenApply(v -> null); + } + + @Override + public MastershipRole getLocalRole(DeviceId deviceId) { + checkPermission(CLUSTER_READ); + + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getRole(clusterService.getLocalNode().id(), deviceId); + } + + @Override + public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) { + checkPermission(CLUSTER_WRITE); + return store.relinquishRole(localNodeId, deviceId) + .thenAccept(this::post) + .thenApply(v -> null); + } + + @Override + public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) { + checkPermission(CLUSTER_WRITE); + + checkNotNull(deviceId, DEVICE_ID_NULL); + final Context timer = startTimer(requestRoleTimer); + return store.requestRole(deviceId).whenComplete((result, error) -> stopTimer(timer)); + + } + + @Override + public NodeId getMasterFor(DeviceId deviceId) { + checkPermission(CLUSTER_READ); + + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getMaster(deviceId); + } + + @Override + public Set<DeviceId> getDevicesOf(NodeId nodeId) { + checkPermission(CLUSTER_READ); + + checkNotNull(nodeId, NODE_ID_NULL); + return store.getDevices(nodeId); + } + + @Override + public RoleInfo getNodesFor(DeviceId deviceId) { + checkPermission(CLUSTER_READ); + + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getNodes(deviceId); + } + + @Override + public MastershipTerm getMastershipTerm(DeviceId deviceId) { + return store.getTermFor(deviceId); + } + + @Override + public MetricsService metricsService() { + return metricsService; + } + + @Override + public void balanceRoles() { + List<ControllerNode> nodes = newArrayList(clusterService.getNodes()); + Map<ControllerNode, Set<DeviceId>> controllerDevices = new HashMap<>(); + int deviceCount = 0; + + // Create buckets reflecting current ownership. + for (ControllerNode node : nodes) { + if (clusterService.getState(node.id()) == ACTIVE) { + Set<DeviceId> devicesOf = new HashSet<>(getDevicesOf(node.id())); + deviceCount += devicesOf.size(); + controllerDevices.put(node, devicesOf); + log.info("Node {} has {} devices.", node.id(), devicesOf.size()); + } + } + + // Now re-balance the buckets until they are roughly even. + List<CompletableFuture<Void>> balanceBucketsFutures = Lists.newLinkedList(); + int rounds = controllerDevices.keySet().size(); + for (int i = 0; i < rounds; i++) { + // Iterate over the buckets and find the smallest and the largest. + ControllerNode smallest = findBucket(true, controllerDevices); + ControllerNode largest = findBucket(false, controllerDevices); + balanceBucketsFutures.add(balanceBuckets(smallest, largest, controllerDevices, deviceCount)); + } + CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf( + balanceBucketsFutures.toArray(new CompletableFuture[balanceBucketsFutures.size()])); + + Futures.getUnchecked(balanceRolesFuture); + } + + private ControllerNode findBucket(boolean min, + Map<ControllerNode, Set<DeviceId>> controllerDevices) { + int xSize = min ? Integer.MAX_VALUE : -1; + ControllerNode xNode = null; + for (ControllerNode node : controllerDevices.keySet()) { + int size = controllerDevices.get(node).size(); + if ((min && size < xSize) || (!min && size > xSize)) { + xSize = size; + xNode = node; + } + } + return xNode; + } + + private CompletableFuture<Void> balanceBuckets(ControllerNode smallest, ControllerNode largest, + Map<ControllerNode, Set<DeviceId>> controllerDevices, + int deviceCount) { + Collection<DeviceId> minBucket = controllerDevices.get(smallest); + Collection<DeviceId> maxBucket = controllerDevices.get(largest); + int bucketCount = controllerDevices.keySet().size(); + + int delta = (maxBucket.size() - minBucket.size()) / 2; + delta = Math.min(deviceCount / bucketCount, delta); + + List<CompletableFuture<Void>> setRoleFutures = Lists.newLinkedList(); + + if (delta > 0) { + log.info("Attempting to move {} nodes from {} to {}...", delta, + largest.id(), smallest.id()); + + int i = 0; + Iterator<DeviceId> it = maxBucket.iterator(); + while (it.hasNext() && i < delta) { + DeviceId deviceId = it.next(); + log.info("Setting {} as the master for {}", smallest.id(), deviceId); + setRoleFutures.add(setRole(smallest.id(), deviceId, MASTER)); + controllerDevices.get(smallest).add(deviceId); + it.remove(); + i++; + } + } + + return CompletableFuture.allOf(setRoleFutures.toArray(new CompletableFuture[setRoleFutures.size()])); + } + + + public class InternalDelegate implements MastershipStoreDelegate { + @Override + public void notify(MastershipEvent event) { + post(event); + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java new file mode 100644 index 00000000..653edaa5 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Subsystem for tracking controller cluster nodes. + */ +package org.onosproject.cluster.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java new file mode 100644 index 00000000..267cd713 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014 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.core.impl; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.onosproject.core.IdBlock; +import org.onosproject.core.IdGenerator; +import org.onosproject.core.UnavailableIdException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Base class of {@link IdGenerator} implementations which use {@link IdBlockAllocator} as + * backend. + */ +public class BlockAllocatorBasedIdGenerator implements IdGenerator { + protected final IdBlockAllocator allocator; + protected IdBlock idBlock; + protected AtomicBoolean initialized; + + + /** + * Constructs an ID generator which use {@link IdBlockAllocator} as backend. + * + * @param allocator the ID block allocator to use + */ + protected BlockAllocatorBasedIdGenerator(IdBlockAllocator allocator) { + this.allocator = checkNotNull(allocator, "allocator cannot be null"); + this.initialized = new AtomicBoolean(false); + } + + @Override + public long getNewId() { + try { + if (!initialized.get()) { + synchronized (allocator) { + if (!initialized.get()) { + idBlock = allocator.allocateUniqueIdBlock(); + initialized.set(true); + } + } + } + return idBlock.getNextId(); + } catch (UnavailableIdException e) { + synchronized (allocator) { + idBlock = allocator.allocateUniqueIdBlock(); + } + return idBlock.getNextId(); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java new file mode 100644 index 00000000..07612292 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java @@ -0,0 +1,190 @@ +/* + * Copyright 2014-2015 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.core.impl; + +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.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.SharedExecutors; +import org.onlab.util.Tools; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.ApplicationIdStore; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdBlockStore; +import org.onosproject.core.IdGenerator; +import org.onosproject.core.Version; +import org.onosproject.event.EventDeliveryService; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Dictionary; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + + +/** + * Core service implementation. + */ +@Component(immediate = true) +@Service +public class CoreManager implements CoreService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private static final File VERSION_FILE = new File("../VERSION"); + private static Version version = Version.version("1.3.0-SNAPSHOT"); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ApplicationIdStore applicationIdStore; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IdBlockStore idBlockStore; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected EventDeliveryService eventDeliveryService; + + private static final int DEFAULT_POOL_SIZE = 30; + @Property(name = "sharedThreadPoolSize", intValue = DEFAULT_POOL_SIZE, + label = "Configure shared pool maximum size ") + private int sharedThreadPoolSize = DEFAULT_POOL_SIZE; + + private static final int DEFAULT_EVENT_TIME = 2000; + @Property(name = "maxEventTimeLimit", intValue = DEFAULT_EVENT_TIME, + label = "Maximum number of millis an event sink has to process an event") + private int maxEventTimeLimit = DEFAULT_EVENT_TIME; + + @Activate + public void activate() { + registerApplication(CORE_APP_NAME); + cfgService.registerProperties(getClass()); + List<String> versionLines = Tools.slurp(VERSION_FILE); + if (versionLines != null && !versionLines.isEmpty()) { + version = Version.version(versionLines.get(0)); + } + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + SharedExecutors.shutdown(); + } + + @Override + public Version version() { + checkPermission(APP_READ); + + return version; + } + + @Override + public Set<ApplicationId> getAppIds() { + checkPermission(APP_READ); + + return applicationIdStore.getAppIds(); + } + + @Override + public ApplicationId getAppId(Short id) { + checkPermission(APP_READ); + + return applicationIdStore.getAppId(id); + } + + @Override + public ApplicationId getAppId(String name) { + checkPermission(APP_READ); + + return applicationIdStore.getAppId(name); + } + + + @Override + public ApplicationId registerApplication(String name) { + checkNotNull(name, "Application ID cannot be null"); + return applicationIdStore.registerApplication(name); + } + + @Override + public IdGenerator getIdGenerator(String topic) { + IdBlockAllocator allocator = new StoreBasedIdBlockAllocator(topic, idBlockStore); + return new BlockAllocatorBasedIdGenerator(allocator); + } + + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + Integer poolSize = getIntegerProperty(properties, "sharedThreadPoolSize"); + + if (poolSize != null && poolSize > 1) { + sharedThreadPoolSize = poolSize; + SharedExecutors.setPoolSize(sharedThreadPoolSize); + } else if (poolSize != null) { + log.warn("sharedThreadPoolSize must be greater than 1"); + } + + Integer timeLimit = getIntegerProperty(properties, "maxEventTimeLimit"); + if (timeLimit != null && timeLimit > 1) { + maxEventTimeLimit = timeLimit; + eventDeliveryService.setDispatchTimeLimit(maxEventTimeLimit); + } else if (timeLimit != null) { + log.warn("maxEventTimeLimit must be greater than 1"); + } + + log.info("Settings: sharedThreadPoolSize={}, maxEventTimeLimit={}", + sharedThreadPoolSize, maxEventTimeLimit); + } + + + /** + * Get Integer property from the propertyName + * Return null if propertyName is not found. + * + * @param properties properties to be looked up + * @param propertyName the name of the property to look up + * @return value when the propertyName is defined or return null + */ + private static Integer getIntegerProperty(Dictionary<?, ?> properties, + String propertyName) { + Integer value = null; + try { + String s = (String) properties.get(propertyName); + value = isNullOrEmpty(s) ? value : Integer.parseInt(s.trim()); + } catch (NumberFormatException | ClassCastException e) { + value = null; + } + return value; + } + + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java new file mode 100644 index 00000000..ba8594f2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 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.core.impl; + +import org.onosproject.core.IdBlock; + +/** + * An interface that gives unique ID spaces. + */ +public interface IdBlockAllocator { + /** + * Allocates a unique Id Block. + * + * @return Id Block. + */ + IdBlock allocateUniqueIdBlock(); + + /** + * Allocates next unique id and retrieve a new range of ids if needed. + * + * @param range range to use for the identifier + * @return Id Block. + */ + IdBlock allocateUniqueIdBlock(long range); +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java new file mode 100644 index 00000000..5a3f08df --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 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.core.impl; + +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.Service; + +import org.onlab.metrics.MetricsManager; + +/** + * Metrics service implementation. + */ +@Component(immediate = true) +@Service +public class MetricsManagerComponent extends MetricsManager { + + @Activate + protected void activate() { + super.clear(); + } + + @Deactivate + protected void deactivate() { + super.clear(); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java new file mode 100644 index 00000000..c28de73d --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 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.core.impl; + +import org.onosproject.core.IdBlock; +import org.onosproject.core.IdBlockStore; + +public class StoreBasedIdBlockAllocator implements IdBlockAllocator { + private final IdBlockStore store; + private final String topic; + + public StoreBasedIdBlockAllocator(String topic, IdBlockStore store) { + this.topic = topic; + this.store = store; + } + + /** + * Returns a block of IDs which are unique and unused. + * Range of IDs is fixed size and is assigned incrementally as this method + * called. + * + * @return an IdBlock containing a set of unique IDs + */ + @Override + public synchronized IdBlock allocateUniqueIdBlock() { + return store.getIdBlock(topic); + } + + @Override + public IdBlock allocateUniqueIdBlock(long range) { + throw new UnsupportedOperationException("Not supported yet"); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java new file mode 100644 index 00000000..f8c3e672 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Miscellaneous core system implementations. + */ +package org.onosproject.core.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java new file mode 100644 index 00000000..79ce74b7 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java @@ -0,0 +1,175 @@ +/* + * Copyright 2014-2015 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.event.impl; + +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.Service; +import org.onlab.util.SharedExecutors; +import org.onosproject.event.AbstractEvent; +import org.onosproject.event.DefaultEventSinkRegistry; +import org.onosproject.event.Event; +import org.onosproject.event.EventDeliveryService; +import org.onosproject.event.EventSink; +import org.slf4j.Logger; + +import java.util.TimerTask; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Simple implementation of an event dispatching service. + */ +@Component(immediate = true) +@Service +public class CoreEventDispatcher extends DefaultEventSinkRegistry + implements EventDeliveryService { + + private final Logger log = getLogger(getClass()); + + // Default number of millis a sink can take to process an event. + private static final long DEFAULT_EXECUTE_MS = 5_000; // ms + private static final long WATCHDOG_MS = 250; // ms + + private final BlockingQueue<Event> events = new LinkedBlockingQueue<>(); + + private final ExecutorService executor = + newSingleThreadExecutor(groupedThreads("onos/event", "dispatch-%d")); + + @SuppressWarnings("unchecked") + private static final Event KILL_PILL = new AbstractEvent(null, 0) { + }; + + private DispatchLoop dispatchLoop; + private long maxProcessMillis = DEFAULT_EXECUTE_MS; + + // Means to detect long-running sinks + private TimerTask watchdog; + private EventSink lastSink; + private long lastStart = 0; + private Future<?> dispatchFuture; + + @Override + public void post(Event event) { + if (!events.add(event)) { + log.error("Unable to post event {}", event); + } + } + + @Activate + public void activate() { + dispatchLoop = new DispatchLoop(); + dispatchFuture = executor.submit(dispatchLoop); + watchdog = new Watchdog(); + SharedExecutors.getTimer().schedule(watchdog, WATCHDOG_MS, WATCHDOG_MS); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + dispatchLoop.stop(); + watchdog.cancel(); + post(KILL_PILL); + log.info("Stopped"); + } + + @Override + public void setDispatchTimeLimit(long millis) { + checkArgument(millis >= WATCHDOG_MS, + "Time limit must be greater than %s", WATCHDOG_MS); + maxProcessMillis = millis; + } + + @Override + public long getDispatchTimeLimit() { + return maxProcessMillis; + } + + // Auxiliary event dispatching loop that feeds off the events queue. + private class DispatchLoop implements Runnable { + private volatile boolean stopped; + + @Override + public void run() { + stopped = false; + log.info("Dispatch loop initiated"); + while (!stopped) { + try { + // Fetch the next event and if it is the kill-pill, bail + Event event = events.take(); + if (event == KILL_PILL) { + break; + } + process(event); + } catch (InterruptedException e) { + log.warn("Dispatch loop interrupted"); + } catch (Exception e) { + log.warn("Error encountered while dispatching event:", e); + } + } + log.info("Dispatch loop terminated"); + } + + // Locate the sink for the event class and use it to process the event + @SuppressWarnings("unchecked") + private void process(Event event) { + EventSink sink = getSink(event.getClass()); + if (sink != null) { + lastSink = sink; + lastStart = System.currentTimeMillis(); + sink.process(event); + lastStart = 0; + } else { + log.warn("No sink registered for event class {}", + event.getClass().getName()); + } + } + + void stop() { + stopped = true; + } + } + + // Monitors event sinks to make sure none take too long to execute. + private class Watchdog extends TimerTask { + @Override + public void run() { + long delta = System.currentTimeMillis() - lastStart; + if (lastStart > 0 && delta > maxProcessMillis) { + lastStart = 0; + log.warn("Event sink {} exceeded execution time limit: {} ms; spawning new dispatch loop", + lastSink.getClass().getName(), delta); + + // Notify the sink that it has exceeded its time limit. + lastSink.onProcessLimit(); + + // Cancel the old dispatch loop and submit a new one. + dispatchLoop.stop(); + dispatchLoop = new DispatchLoop(); + dispatchFuture.cancel(true); + dispatchFuture = executor.submit(dispatchLoop); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java new file mode 100644 index 00000000..f0882f3d --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Local event dispatching mechanism. + */ +package org.onosproject.event.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java new file mode 100644 index 00000000..de2f5c3b --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 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.net.config.impl; + +import com.google.common.collect.ImmutableSet; +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.onosproject.core.CoreService; +import org.onosproject.incubator.net.config.basics.InterfaceConfig; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.HostId; +import org.onosproject.net.LinkKey; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigRegistry; +import org.onosproject.net.config.basics.BasicDeviceConfig; +import org.onosproject.net.config.basics.BasicHostConfig; +import org.onosproject.net.config.basics.BasicLinkConfig; +import org.onosproject.net.config.basics.OpticalPortConfig; +import org.onosproject.net.config.basics.SubjectFactories; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; + +import static org.onosproject.net.config.basics.SubjectFactories.*; + +/** + * Component for registration of builtin basic network configurations. + */ +@Component(immediate = true) +public class BasicNetworkConfigs { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final Set<ConfigFactory> factories = ImmutableSet.of( + new ConfigFactory<DeviceId, BasicDeviceConfig>(DEVICE_SUBJECT_FACTORY, + BasicDeviceConfig.class, + "basic") { + @Override + public BasicDeviceConfig createConfig() { + return new BasicDeviceConfig(); + } + }, + new ConfigFactory<ConnectPoint, InterfaceConfig>(CONNECT_POINT_SUBJECT_FACTORY, + InterfaceConfig.class, + "interfaces", + true) { + @Override + public InterfaceConfig createConfig() { + return new InterfaceConfig(); + } + }, + new ConfigFactory<HostId, BasicHostConfig>(HOST_SUBJECT_FACTORY, + BasicHostConfig.class, + "basic") { + @Override + public BasicHostConfig createConfig() { + return new BasicHostConfig(); + } + }, + new ConfigFactory<LinkKey, BasicLinkConfig>(LINK_SUBJECT_FACTORY, + BasicLinkConfig.class, + "basic") { + @Override + public BasicLinkConfig createConfig() { + return new BasicLinkConfig(); + } + }, + new ConfigFactory<ConnectPoint, OpticalPortConfig>(CONNECT_POINT_SUBJECT_FACTORY, + OpticalPortConfig.class, + "optical") { + @Override + public OpticalPortConfig createConfig() { + return new OpticalPortConfig(); + } + } + ); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigRegistry registry; + + @Activate + public void activate() { + SubjectFactories.setCoreService(coreService); + factories.forEach(registry::registerConfigFactory); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + factories.forEach(registry::unregisterConfigFactory); + log.info("Stopped"); + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java new file mode 100644 index 00000000..01348c15 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java @@ -0,0 +1,218 @@ +/* + * Copyright 2015 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.net.config.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Maps; +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.onosproject.net.config.Config; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** + * Component for loading the initial network configuration. + */ +@Component(immediate = true) +public class NetworkConfigLoader { + + private static final File CFG_FILE = new File("../config/network-cfg.json"); + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService networkConfigService; + + // FIXME: Add mutual exclusion to make sure this happens only once per startup. + + private final Map<InnerConfigPosition, JsonNode> jsons = Maps.newConcurrentMap(); + + private final NetworkConfigListener configListener = new InnerConfigListener(); + + private ObjectNode root; + + @Activate + public void activate() { + //TODO Maybe this should be at the bottom to avoid a potential race + networkConfigService.addListener(configListener); + try { + if (CFG_FILE.exists()) { + root = (ObjectNode) new ObjectMapper().readTree(CFG_FILE); + + populateConfigurations(); + + applyConfigurations(); + + log.info("Loaded initial network configuration from {}", CFG_FILE); + } + } catch (Exception e) { + log.warn("Unable to load initial network configuration from {}", + CFG_FILE, e); + } + } + + @Deactivate + public void deactivate() { + networkConfigService.removeListener(configListener); + } + // sweep through pending config jsons and try to add them + + /** + * Inner class that allows for handling of newly added NetConfig types. + */ + private final class InnerConfigListener implements NetworkConfigListener { + + @Override + public void event(NetworkConfigEvent event) { + //TODO should this be done for other types of NetworkConfigEvents? + if (event.type() == NetworkConfigEvent.Type.CONFIG_REGISTERED || + event.type() == NetworkConfigEvent.Type.CONFIG_ADDED) { + applyConfigurations(); + } + + } + } + + /** + * Inner class that allows for tracking of JSON class configurations. + */ + private final class InnerConfigPosition { + private final String subjectKey, subject, configKey; + + private String subjectKey() { + return subjectKey; + } + + private String subject() { + return subject; + } + + private String configKey() { + return configKey; + } + + private InnerConfigPosition(String subjectKey, String subject, String configKey) { + this.subjectKey = subjectKey; + this.subject = subject; + this.configKey = configKey; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof InnerConfigPosition) { + final InnerConfigPosition that = (InnerConfigPosition) obj; + return Objects.equals(this.subjectKey, that.subjectKey) + && Objects.equals(this.subject, that.subject) + && Objects.equals(this.configKey, that.configKey); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(subjectKey, subject, configKey); + } + } + + /** + * Save the JSON leaves associated with a specific subject key. + * + * @param sk the subject key string. + * @param node the node associated with the subject key. + */ + private void saveJson(String sk, ObjectNode node) { + node.fieldNames().forEachRemaining(s -> + saveSubjectJson(sk, s, (ObjectNode) node.path(s))); + } + + /** + * Save the JSON leaves of the tree rooted as the node 'node' with subject key 'sk'. + * + * @param sk the string of the subject key. + * @param s the subject name. + * @param node the node rooting this subtree. + */ + private void saveSubjectJson(String sk, + String s, ObjectNode node) { + node.fieldNames().forEachRemaining(c -> + this.jsons.put(new InnerConfigPosition(sk, s, c), node.path(c))); + } + + /** + * Iterate through the JSON and populate a list of the leaf nodes of the structure. + */ + private void populateConfigurations() { + root.fieldNames().forEachRemaining(sk -> + saveJson(sk, (ObjectNode) root.path(sk))); + + } + + /** + * Apply the configurations associated with all of the config classes that + * are imported and have not yet been applied. + */ + private void applyConfigurations() { + Iterator<Map.Entry<InnerConfigPosition, JsonNode>> iter = jsons.entrySet().iterator(); + + Map.Entry<InnerConfigPosition, JsonNode> entry; + InnerConfigPosition key; + JsonNode node; + String subjectKey; + String subjectString; + String configKey; + + while (iter.hasNext()) { + entry = iter.next(); + node = entry.getValue(); + key = entry.getKey(); + subjectKey = key.subjectKey(); + subjectString = key.subject(); + configKey = key.configKey(); + + Class<? extends Config> configClass = + networkConfigService.getConfigClass(subjectKey, configKey); + //Check that the config class has been imported + if (configClass != null) { + + Object subject = networkConfigService.getSubjectFactory(subjectKey). + createSubject(subjectString); + + //Apply the configuration + networkConfigService.applyConfig(subject, configClass, node); + + //Now that it has been applied the corresponding JSON entry is no longer needed + iter.remove(); + } + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java new file mode 100644 index 00000000..5cd96cab --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java @@ -0,0 +1,288 @@ +/* + * Copyright 2015 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.net.config.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +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.onosproject.event.AbstractListenerManager; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigRegistry; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.NetworkConfigStore; +import org.onosproject.net.config.NetworkConfigStoreDelegate; +import org.onosproject.net.config.SubjectFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Implementation of the network configuration subsystem. + */ +@Component(immediate = true) +@Service +public class NetworkConfigManager + extends AbstractListenerManager<NetworkConfigEvent, NetworkConfigListener> + implements NetworkConfigRegistry, NetworkConfigService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private static final String NULL_FACTORY_MSG = "Factory cannot be null"; + private static final String NULL_SCLASS_MSG = "Subject class cannot be null"; + private static final String NULL_CCLASS_MSG = "Config class cannot be null"; + private static final String NULL_SUBJECT_MSG = "Subject cannot be null"; + + // Inventory of configuration factories + private final Map<ConfigKey, ConfigFactory> factories = Maps.newConcurrentMap(); + + // Secondary indices to retrieve subject and config classes by keys + private final Map<String, SubjectFactory> subjectClasses = Maps.newConcurrentMap(); + private final Map<Class, SubjectFactory> subjectClassKeys = Maps.newConcurrentMap(); + private final Map<ConfigIdentifier, Class<? extends Config>> configClasses = Maps.newConcurrentMap(); + + private final NetworkConfigStoreDelegate storeDelegate = new InternalStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigStore store; + + + @Activate + public void activate() { + eventDispatcher.addSink(NetworkConfigEvent.class, listenerRegistry); + store.setDelegate(storeDelegate); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + eventDispatcher.removeSink(NetworkConfigEvent.class); + store.unsetDelegate(storeDelegate); + log.info("Stopped"); + } + + + @Override + @SuppressWarnings("unchecked") + public void registerConfigFactory(ConfigFactory configFactory) { + checkNotNull(configFactory, NULL_FACTORY_MSG); + factories.put(key(configFactory), configFactory); + configClasses.put(identifier(configFactory), configFactory.configClass()); + + SubjectFactory subjectFactory = configFactory.subjectFactory(); + subjectClasses.putIfAbsent(subjectFactory.subjectKey(), subjectFactory); + subjectClassKeys.putIfAbsent(subjectFactory.subjectClass(), subjectFactory); + + store.addConfigFactory(configFactory); + } + + @Override + public void unregisterConfigFactory(ConfigFactory configFactory) { + checkNotNull(configFactory, NULL_FACTORY_MSG); + factories.remove(key(configFactory)); + configClasses.remove(identifier(configFactory)); + + // Note that we are deliberately not removing subject factory key bindings. + store.removeConfigFactory(configFactory); + } + + @Override + public Set<ConfigFactory> getConfigFactories() { + return ImmutableSet.copyOf(factories.values()); + } + + + @Override + @SuppressWarnings("unchecked") + public <S, C extends Config<S>> Set<ConfigFactory<S, C>> getConfigFactories(Class<S> subjectClass) { + ImmutableSet.Builder<ConfigFactory<S, C>> builder = ImmutableSet.builder(); + factories.forEach((key, factory) -> { + if (factory.subjectFactory().subjectClass().equals(subjectClass)) { + builder.add(factory); + } + }); + return builder.build(); + } + + @Override + public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) { + checkNotNull(configClass, NULL_CCLASS_MSG); + return store.getConfigFactory(configClass); + } + + + @Override + public Set<Class> getSubjectClasses() { + ImmutableSet.Builder<Class> builder = ImmutableSet.builder(); + factories.forEach((k, v) -> builder.add(k.subjectClass)); + return builder.build(); + } + + @Override + public SubjectFactory getSubjectFactory(String subjectKey) { + return subjectClasses.get(subjectKey); + } + + @Override + public SubjectFactory getSubjectFactory(Class subjectClass) { + return subjectClassKeys.get(subjectClass); + } + + @Override + public Class<? extends Config> getConfigClass(String subjectKey, String configKey) { + return configClasses.get(new ConfigIdentifier(subjectKey, configKey)); + } + + @Override + public <S> Set<S> getSubjects(Class<S> subjectClass) { + checkNotNull(subjectClass, NULL_SCLASS_MSG); + return store.getSubjects(subjectClass); + } + + @Override + public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) { + checkNotNull(subjectClass, NULL_SCLASS_MSG); + checkNotNull(configClass, NULL_CCLASS_MSG); + return store.getSubjects(subjectClass, configClass); + } + + @Override + public <S> Set<Config<S>> getConfigs(S subject) { + checkNotNull(subject, NULL_SUBJECT_MSG); + Set<Class<? extends Config<S>>> configClasses = store.getConfigClasses(subject); + ImmutableSet.Builder<Config<S>> cfg = ImmutableSet.builder(); + configClasses.forEach(cc -> cfg.add(store.getConfig(subject, cc))); + return cfg.build(); + } + + @Override + public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) { + checkNotNull(subject, NULL_SUBJECT_MSG); + checkNotNull(configClass, NULL_CCLASS_MSG); + return store.getConfig(subject, configClass); + } + + + @Override + public <S, C extends Config<S>> C addConfig(S subject, Class<C> configClass) { + checkNotNull(subject, NULL_SUBJECT_MSG); + checkNotNull(configClass, NULL_CCLASS_MSG); + return store.createConfig(subject, configClass); + } + + @Override + public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) { + checkNotNull(subject, NULL_SUBJECT_MSG); + checkNotNull(configClass, NULL_CCLASS_MSG); + return store.applyConfig(subject, configClass, json); + } + + @Override + public <S, C extends Config<S>> void removeConfig(S subject, Class<C> configClass) { + checkNotNull(subject, NULL_SUBJECT_MSG); + checkNotNull(configClass, NULL_CCLASS_MSG); + store.clearConfig(subject, configClass); + } + + // Auxiliary store delegate to receive notification about changes in + // the network configuration store state - by the store itself. + private class InternalStoreDelegate implements NetworkConfigStoreDelegate { + @Override + public void notify(NetworkConfigEvent event) { + post(event); + } + } + + + // Produces a key for uniquely tracking a config factory. + private static ConfigKey key(ConfigFactory factory) { + return new ConfigKey(factory.subjectFactory().subjectClass(), factory.configClass()); + } + + // Auxiliary key to track config factories. + protected static final class ConfigKey { + final Class subjectClass; + final Class configClass; + + protected ConfigKey(Class subjectClass, Class configClass) { + this.subjectClass = subjectClass; + this.configClass = configClass; + } + + @Override + public int hashCode() { + return Objects.hash(subjectClass, configClass); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ConfigKey) { + final ConfigKey other = (ConfigKey) obj; + return Objects.equals(this.subjectClass, other.subjectClass) + && Objects.equals(this.configClass, other.configClass); + } + return false; + } + } + + private static ConfigIdentifier identifier(ConfigFactory factory) { + return new ConfigIdentifier(factory.subjectFactory().subjectKey(), factory.configKey()); + } + + static final class ConfigIdentifier { + final String subjectKey; + final String configKey; + + protected ConfigIdentifier(String subjectKey, String configKey) { + this.subjectKey = subjectKey; + this.configKey = configKey; + } + + @Override + public int hashCode() { + return Objects.hash(subjectKey, configKey); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ConfigIdentifier) { + final ConfigIdentifier other = (ConfigIdentifier) obj; + return Objects.equals(this.subjectKey, other.subjectKey) + && Objects.equals(this.configKey, other.configKey); + } + return false; + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java new file mode 100644 index 00000000..38f76941 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementation of the network configuration subsystem. + */ +package org.onosproject.net.config.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java new file mode 100644 index 00000000..7900d185 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java @@ -0,0 +1,107 @@ +/* + * Copyright 2014-2015 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.net.device.impl; + +import static org.slf4j.LoggerFactory.getLogger; +import static com.google.common.base.Preconditions.checkNotNull; + +import org.onosproject.net.config.ConfigOperator; +import org.onosproject.net.config.basics.BasicDeviceConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; +import org.slf4j.Logger; + +import java.util.Objects; + +/** + * Implementations of merge policies for various sources of device configuration + * information. This includes applications, provides, and network configurations. + */ +public final class BasicDeviceOperator implements ConfigOperator { + + protected static final double DEFAULT_COORD = -1.0; + private static final Logger log = getLogger(BasicDeviceOperator.class); + + private BasicDeviceOperator() { + } + + /** + * Generates a DeviceDescription containing fields from a DeviceDescription and + * a DeviceConfig. + * + * @param bdc the device config entity from network config + * @param descr a DeviceDescription + * @return DeviceDescription based on both sources + */ + public static DeviceDescription combine(BasicDeviceConfig bdc, DeviceDescription descr) { + if (bdc == null) { + return descr; + } + + Device.Type type = descr.type(); + if (bdc.type() != null && bdc.type() != type) { + type = bdc.type(); + } + + SparseAnnotations sa = combine(bdc, descr.annotations()); + return new DefaultDeviceDescription(descr.deviceURI(), type, descr.manufacturer(), + descr.hwVersion(), descr.swVersion(), + descr.serialNumber(), descr.chassisId(), sa); + } + + /** + * Generates an annotation from an existing annotation and DeviceConfig. + * + * @param bdc the device config entity from network config + * @param an the annotation + * @return annotation combining both sources + */ + public static SparseAnnotations combine(BasicDeviceConfig bdc, SparseAnnotations an) { + DefaultAnnotations.Builder newBuilder = DefaultAnnotations.builder(); + if (!Objects.equals(bdc.driver(), an.value(AnnotationKeys.DRIVER))) { + newBuilder.set(AnnotationKeys.DRIVER, bdc.driver()); + } + if (bdc.name() != null) { + newBuilder.set(AnnotationKeys.NAME, bdc.name()); + } + if (bdc.latitude() != DEFAULT_COORD) { + newBuilder.set(AnnotationKeys.LATITUDE, Double.toString(bdc.latitude())); + } + if (bdc.longitude() != DEFAULT_COORD) { + newBuilder.set(AnnotationKeys.LONGITUDE, Double.toString(bdc.longitude())); + } + if (bdc.rackAddress() != null) { + newBuilder.set(AnnotationKeys.RACK_ADDRESS, bdc.rackAddress()); + } + if (bdc.owner() != null) { + newBuilder.set(AnnotationKeys.OWNER, bdc.owner()); + } + DefaultAnnotations newAnnotations = newBuilder.build(); + return DefaultAnnotations.union(an, newAnnotations); + } + + public static DeviceDescription descriptionOf(Device device) { + checkNotNull(device, "Must supply non-null Device"); + return new DefaultDeviceDescription(device.id().uri(), device.type(), + device.manufacturer(), device.hwVersion(), + device.swVersion(), device.serialNumber(), + device.chassisId(), (SparseAnnotations) device.annotations()); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java new file mode 100644 index 00000000..b0b3abe2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java @@ -0,0 +1,765 @@ +/* + * Copyright 2014-2015 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.net.device.impl; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; + +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.onosproject.cluster.ClusterService; +import org.onosproject.cluster.NodeId; +import org.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.basics.BasicDeviceConfig; +import org.onosproject.net.config.basics.OpticalPortConfig; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.mastership.MastershipTerm; +import org.onosproject.mastership.MastershipTermService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.Device.Type; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.DeviceStore; +import org.onosproject.net.device.DeviceStoreDelegate; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.MastershipRole.*; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + +/** + * Provides implementation of the device SB & NB APIs. + */ +@Component(immediate = true) +@Service +public class DeviceManager + extends AbstractListenerProviderRegistry<DeviceEvent, DeviceListener, DeviceProvider, DeviceProviderService> + implements DeviceService, DeviceAdminService, DeviceProviderRegistry { + + private static final String DEVICE_ID_NULL = "Device ID cannot be null"; + private static final String PORT_NUMBER_NULL = "Port number cannot be null"; + private static final String DEVICE_DESCRIPTION_NULL = "Device description cannot be null"; + private static final String PORT_DESCRIPTION_NULL = "Port description cannot be null"; + private static final String PORT_DESC_LIST_NULL = "Port description list cannot be null"; + + private final Logger log = getLogger(getClass()); + + private final DeviceStoreDelegate delegate = new InternalStoreDelegate(); + + private final MastershipListener mastershipListener = new InternalMastershipListener(); + private NodeId localNodeId; + + private ScheduledExecutorService backgroundService; + + private final NetworkConfigListener networkConfigListener = new InternalNetworkConfigListener(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipTermService termService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService networkConfigService; + + @Activate + public void activate() { + backgroundService = newSingleThreadScheduledExecutor(groupedThreads("onos/device", "manager-background")); + localNodeId = clusterService.getLocalNode().id(); + + store.setDelegate(delegate); + eventDispatcher.addSink(DeviceEvent.class, listenerRegistry); + mastershipService.addListener(mastershipListener); + networkConfigService.addListener(networkConfigListener); + + backgroundService.scheduleWithFixedDelay(() -> { + try { + mastershipCheck(); + } catch (Exception e) { + log.error("Exception thrown during integrity check", e); + } + }, 1, 1, TimeUnit.MINUTES); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + backgroundService.shutdown(); + networkConfigService.removeListener(networkConfigListener); + store.unsetDelegate(delegate); + mastershipService.removeListener(mastershipListener); + eventDispatcher.removeSink(DeviceEvent.class); + log.info("Stopped"); + } + + @Override + public int getDeviceCount() { + checkPermission(DEVICE_READ); + return store.getDeviceCount(); + } + + @Override + public Iterable<Device> getDevices() { + checkPermission(DEVICE_READ); + return store.getDevices(); + } + + @Override + public Iterable<Device> getAvailableDevices() { + checkPermission(DEVICE_READ); + return store.getAvailableDevices(); + } + + @Override + public Device getDevice(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getDevice(deviceId); + } + + @Override + public MastershipRole getRole(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return mastershipService.getLocalRole(deviceId); + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getPorts(deviceId); + } + + @Override + public List<PortStatistics> getPortStatistics(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getPortStatistics(deviceId); + } + + @Override + public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getPortDeltaStatistics(deviceId); + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portNumber, PORT_NUMBER_NULL); + return store.getPort(deviceId, portNumber); + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + checkPermission(DEVICE_READ); + + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.isAvailable(deviceId); + } + + // Check a device for control channel connectivity. + private boolean isReachable(DeviceId deviceId) { + if (deviceId == null) { + return false; + } + DeviceProvider provider = getProvider(deviceId); + if (provider != null) { + return provider.isReachable(deviceId); + } else { + log.debug("Provider not found for {}", deviceId); + return false; + } + } + + @Override + public void removeDevice(DeviceId deviceId) { + checkNotNull(deviceId, DEVICE_ID_NULL); + DeviceEvent event = store.removeDevice(deviceId); + if (event != null) { + log.info("Device {} administratively removed", deviceId); + post(event); + } + } + + @Override + protected DeviceProviderService createProviderService( + DeviceProvider provider) { + return new InternalDeviceProviderService(provider); + } + + /** + * Checks if all the reachable devices have a valid mastership role. + */ + private void mastershipCheck() { + log.debug("Checking mastership"); + for (Device device : getDevices()) { + final DeviceId deviceId = device.id(); + log.trace("Checking device {}", deviceId); + + if (!isReachable(deviceId)) { + continue; + } + + if (mastershipService.getLocalRole(deviceId) != NONE) { + continue; + } + + log.info("{} is reachable but did not have a valid role, reasserting", deviceId); + + // isReachable but was not MASTER or STANDBY, get a role and apply + // Note: NONE triggers request to MastershipService + reassertRole(deviceId, NONE); + } + } + + // Personalized device provider service issued to the supplied provider. + private class InternalDeviceProviderService + extends AbstractProviderService<DeviceProvider> + implements DeviceProviderService { + + InternalDeviceProviderService(DeviceProvider provider) { + super(provider); + } + + /** + * Apply role in reaction to provider event. + * + * @param deviceId device identifier + * @param newRole new role to apply to the device + * @return true if the request was sent to provider + */ + private boolean applyRole(DeviceId deviceId, MastershipRole newRole) { + + if (newRole.equals(MastershipRole.NONE)) { + //no-op + return true; + } + + DeviceProvider provider = provider(); + if (provider == null) { + log.warn("Provider for {} was not found. Cannot apply role {}", + deviceId, newRole); + return false; + } + provider.roleChanged(deviceId, newRole); + // not triggering probe when triggered by provider service event + + return true; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(deviceDescription, DEVICE_DESCRIPTION_NULL); + checkValidity(); + + BasicDeviceConfig cfg = networkConfigService.getConfig(deviceId, BasicDeviceConfig.class); + if (!isAllowed(cfg)) { + log.warn("Device {} is not allowed", deviceId); + return; + } + // Generate updated description and establish my Role + deviceDescription = BasicDeviceOperator.combine(cfg, deviceDescription); + Futures.getUnchecked(mastershipService.requestRoleFor(deviceId) + .thenAccept(role -> { + log.info("Local role is {} for {}", role, deviceId); + applyRole(deviceId, role); + })); + + DeviceEvent event = store.createOrUpdateDevice(provider().id(), deviceId, + deviceDescription); + log.info("Device {} connected", deviceId); + if (event != null) { + log.trace("event: {} {}", event.type(), event); + post(event); + } + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkValidity(); + + log.info("Device {} disconnected from this node", deviceId); + + List<Port> ports = store.getPorts(deviceId); + List<PortDescription> descs = Lists.newArrayList(); + ports.forEach(port -> + descs.add(new DefaultPortDescription(port.number(), + false, port.type(), + port.portSpeed()))); + store.updatePorts(this.provider().id(), deviceId, descs); + try { + if (mastershipService.isLocalMaster(deviceId)) { + post(store.markOffline(deviceId)); + } + } catch (IllegalStateException e) { + log.warn("Failed to mark {} offline", deviceId); + // only the MASTER should be marking off-line in normal cases, + // but if I was the last STANDBY connection, etc. and no one else + // was there to mark the device offline, this instance may need to + // temporarily request for Master Role and mark offline. + + //there are times when this node will correctly have mastership, BUT + //that isn't reflected in the ClockManager before the device disconnects. + //we want to let go of the device anyways, so make sure this happens. + + // FIXME: Store semantics leaking out as IllegalStateException. + // Consider revising store API to handle this scenario. + CompletableFuture<MastershipRole> roleFuture = mastershipService.requestRoleFor(deviceId); + roleFuture.whenComplete((role, error) -> { + MastershipTerm term = termService.getMastershipTerm(deviceId); + // TODO: Move this type of check inside device clock manager, etc. + if (term != null && localNodeId.equals(term.master())) { + log.info("Retry marking {} offline", deviceId); + post(store.markOffline(deviceId)); + } else { + log.info("Failed again marking {} offline. {}", deviceId, role); + } + }); + } finally { + try { + //relinquish master role and ability to be backup. + mastershipService.relinquishMastership(deviceId).get(); + } catch (InterruptedException e) { + log.warn("Interrupted while reliquishing role for {}", deviceId); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + log.error("Exception thrown while relinquishing role for {}", deviceId, e); + } + } + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portDescriptions, PORT_DESC_LIST_NULL); + checkValidity(); + if (!mastershipService.isLocalMaster(deviceId)) { + // Never been a master for this device + // any update will be ignored. + log.trace("Ignoring {} port updates on standby node. {}", deviceId, portDescriptions); + return; + } + portDescriptions = portDescriptions.stream() + .map(e -> consolidate(deviceId, e)) + .collect(Collectors.toList()); + List<DeviceEvent> events = store.updatePorts(this.provider().id(), + deviceId, portDescriptions); + for (DeviceEvent event : events) { + post(event); + } + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portDescription, PORT_DESCRIPTION_NULL); + checkValidity(); + + if (!mastershipService.isLocalMaster(deviceId)) { + // Never been a master for this device + // any update will be ignored. + log.trace("Ignoring {} port update on standby node. {}", deviceId, + portDescription); + return; + } + portDescription = consolidate(deviceId, portDescription); + final DeviceEvent event = store.updatePortStatus(this.provider().id(), + deviceId, portDescription); + if (event != null) { + log.info("Device {} port {} status changed", deviceId, event.port().number()); + post(event); + } + } + + // merges the appropriate PortConfig with the description. + private PortDescription consolidate(DeviceId did, PortDescription desc) { + switch (desc.type()) { + case COPPER: + case VIRTUAL: + return desc; + default: + OpticalPortConfig opc = networkConfigService.getConfig( + new ConnectPoint(did, desc.portNumber()), OpticalPortConfig.class); + return OpticalPortOperator.combine(opc, desc); + } + } + + @Override + public void receivedRoleReply(DeviceId deviceId, MastershipRole requested, + MastershipRole response) { + // Several things can happen here: + // 1. request and response match + // 2. request and response don't match + // 3. MastershipRole and requested match (and 1 or 2 are true) + // 4. MastershipRole and requested don't match (and 1 or 2 are true) + // + // 2, 4, and 3 with case 2 are failure modes. + + // FIXME: implement response to this notification + + log.debug("got reply to a role request for {}: asked for {}, and got {}", + deviceId, requested, response); + + if (requested == null && response == null) { + // something was off with DeviceProvider, maybe check channel too? + log.warn("Failed to assert role [{}] onto Device {}", requested, deviceId); + mastershipService.relinquishMastership(deviceId); + return; + } + + if (Objects.equals(requested, response)) { + if (Objects.equals(requested, mastershipService.getLocalRole(deviceId))) { + return; + } else { + return; + // FIXME roleManager got the device to comply, but doesn't agree with + // the store; use the store's view, then try to reassert. + } + } else { + // we didn't get back what we asked for. Reelect someone else. + log.warn("Failed to assert role [{}] onto Device {}", response, deviceId); + if (response == MastershipRole.MASTER) { + mastershipService.relinquishMastership(deviceId); + // TODO: Shouldn't we be triggering event? + //final Device device = getDevice(deviceId); + //post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device)); + } + } + } + + @Override + public void updatePortStatistics(DeviceId deviceId, Collection<PortStatistics> portStatistics) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portStatistics, "Port statistics list cannot be null"); + checkValidity(); + + DeviceEvent event = store.updatePortStatistics(this.provider().id(), + deviceId, portStatistics); + post(event); + } + } + + // by default allowed, otherwise check flag + private boolean isAllowed(BasicDeviceConfig cfg) { + return (cfg == null || cfg.isAllowed()); + } + + // Applies the specified role to the device; ignores NONE + + /** + * Apply role to device and send probe if MASTER. + * + * @param deviceId device identifier + * @param newRole new role to apply to the device + * @return true if the request was sent to provider + */ + private boolean applyRoleAndProbe(DeviceId deviceId, MastershipRole newRole) { + if (newRole.equals(MastershipRole.NONE)) { + //no-op + return true; + } + + DeviceProvider provider = getProvider(deviceId); + if (provider == null) { + log.warn("Provider for {} was not found. Cannot apply role {}", deviceId, newRole); + return false; + } + provider.roleChanged(deviceId, newRole); + + if (newRole.equals(MastershipRole.MASTER)) { + // only trigger event when request was sent to provider + provider.triggerProbe(deviceId); + } + return true; + } + + /** + * Reaasert role for specified device connected to this node. + * + * @param did device identifier + * @param nextRole role to apply. If NONE is specified, + * it will ask mastership service for a role and apply it. + */ + private void reassertRole(final DeviceId did, + final MastershipRole nextRole) { + + MastershipRole myNextRole = nextRole; + if (myNextRole == NONE) { + mastershipService.requestRoleFor(did); + MastershipTerm term = termService.getMastershipTerm(did); + if (term != null && localNodeId.equals(term.master())) { + myNextRole = MASTER; + } else { + myNextRole = STANDBY; + } + } + + switch (myNextRole) { + case MASTER: + final Device device = getDevice(did); + if ((device != null) && !isAvailable(did)) { + //flag the device as online. Is there a better way to do this? + DefaultDeviceDescription deviceDescription + = new DefaultDeviceDescription(did.uri(), + device.type(), + device.manufacturer(), + device.hwVersion(), + device.swVersion(), + device.serialNumber(), + device.chassisId()); + DeviceEvent devEvent = + store.createOrUpdateDevice(device.providerId(), did, + deviceDescription); + post(devEvent); + } + // TODO: should apply role only if there is mismatch + log.debug("Applying role {} to {}", myNextRole, did); + if (!applyRoleAndProbe(did, MASTER)) { + log.warn("Unsuccessful applying role {} to {}", myNextRole, did); + // immediately failed to apply role + mastershipService.relinquishMastership(did); + // FIXME disconnect? + } + break; + case STANDBY: + log.debug("Applying role {} to {}", myNextRole, did); + if (!applyRoleAndProbe(did, STANDBY)) { + log.warn("Unsuccessful applying role {} to {}", myNextRole, did); + // immediately failed to apply role + mastershipService.relinquishMastership(did); + // FIXME disconnect? + } + break; + case NONE: + default: + // should never reach here + log.error("You didn't see anything. I did not exist."); + break; + } + } + + private void handleMastershipEvent(MastershipEvent event) { + if (event.type() != MastershipEvent.Type.MASTER_CHANGED) { + // Don't care if backup list changed. + return; + } + + final DeviceId did = event.subject(); + + // myRole suggested by MastershipService + MastershipRole myNextRole; + if (localNodeId.equals(event.roleInfo().master())) { + // confirm latest info + MastershipTerm term = termService.getMastershipTerm(did); + final boolean iHaveControl = term != null && localNodeId.equals(term.master()); + if (iHaveControl) { + myNextRole = MASTER; + } else { + myNextRole = STANDBY; + } + } else if (event.roleInfo().backups().contains(localNodeId)) { + myNextRole = STANDBY; + } else { + myNextRole = NONE; + } + + final boolean isReachable = isReachable(did); + if (!isReachable) { + // device is not connected to this node + if (myNextRole != NONE) { + log.warn("Node was instructed to be {} role for {}, " + + "but this node cannot reach the device. " + + "Relinquishing role. ", + myNextRole, did); + mastershipService.relinquishMastership(did); + } + return; + } + + // device is connected to this node: + if (store.getDevice(did) != null) { + reassertRole(did, myNextRole); + } else { + log.debug("Device is not yet/no longer in the store: {}", did); + } + } + + // Intercepts mastership events + private class InternalMastershipListener implements MastershipListener { + + @Override + public void event(MastershipEvent event) { + backgroundService.submit(() -> { + try { + handleMastershipEvent(event); + } catch (Exception e) { + log.warn("Failed to handle {}", event, e); + } + }); + } + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements DeviceStoreDelegate { + @Override + public void notify(DeviceEvent event) { + post(event); + } + } + + @Override + public Iterable<Device> getDevices(Type type) { + checkPermission(DEVICE_READ); + Set<Device> results = new HashSet<>(); + Iterable<Device> devices = store.getDevices(); + if (devices != null) { + devices.forEach(d -> { + if (type.equals(d.type())) { + results.add(d); + } + }); + } + return results; + } + + @Override + public Iterable<Device> getAvailableDevices(Type type) { + checkPermission(DEVICE_READ); + Set<Device> results = new HashSet<>(); + Iterable<Device> availableDevices = store.getAvailableDevices(); + if (availableDevices != null) { + availableDevices.forEach(d -> { + if (type.equals(d.type())) { + results.add(d); + } + }); + } + return results; + } + + private class InternalNetworkConfigListener implements NetworkConfigListener { + @Override + public boolean isRelevant(NetworkConfigEvent event) { + return (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED + || event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) + && (event.configClass().equals(BasicDeviceConfig.class) + || event.configClass().equals(OpticalPortConfig.class)); + } + + @Override + public void event(NetworkConfigEvent event) { + DeviceEvent de = null; + if (event.configClass().equals(BasicDeviceConfig.class)) { + log.info("Detected Device network config event {}", event.type()); + DeviceId did = (DeviceId) event.subject(); + BasicDeviceConfig cfg = networkConfigService.getConfig(did, BasicDeviceConfig.class); + + if (!isAllowed(cfg)) { + kickOutBadDevice(did); + } else { + Device dev = getDevice(did); + DeviceDescription desc = (dev == null) ? null : BasicDeviceOperator.descriptionOf(dev); + desc = BasicDeviceOperator.combine(cfg, desc); + if (getProvider(did) != null) { + de = store.createOrUpdateDevice(getProvider(did).id(), did, desc); + } + } + } + if (event.configClass().equals(OpticalPortConfig.class)) { + ConnectPoint cpt = (ConnectPoint) event.subject(); + DeviceId did = cpt.deviceId(); + Port dpt = getPort(did, cpt.port()); + + if (dpt != null) { + OpticalPortConfig opc = networkConfigService.getConfig(cpt, OpticalPortConfig.class); + PortDescription desc = OpticalPortOperator.descriptionOf(dpt); + desc = OpticalPortOperator.combine(opc, desc); + if (getProvider(did) != null) { + de = store.updatePortStatus(getProvider(did).id(), did, desc); + } + } + } + + if (de != null) { + post(de); + } + } + + // checks if the specified device is allowed by the BasicDeviceConfig + // and if not, removes it + private void kickOutBadDevice(DeviceId deviceId) { + Device badDevice = getDevice(deviceId); + if (badDevice != null) { + removeDevice(deviceId); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java new file mode 100644 index 00000000..b2fd02c7 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java @@ -0,0 +1,173 @@ +/* + * Copyright 2014-2015 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.net.device.impl; + +import static org.slf4j.LoggerFactory.getLogger; +import static com.google.common.base.Preconditions.checkNotNull; + +import org.onosproject.net.config.ConfigOperator; +import org.onosproject.net.config.basics.OpticalPortConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.OchPort; +import org.onosproject.net.OduCltPort; +import org.onosproject.net.OmsPort; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.OchPortDescription; +import org.onosproject.net.device.OduCltPortDescription; +import org.onosproject.net.device.OmsPortDescription; +import org.onosproject.net.device.PortDescription; +import org.slf4j.Logger; + +/** + * Implementations of merge policies for various sources of optical port + * configuration information. This includes applications, provides, and network + * configurations. + */ +public final class OpticalPortOperator implements ConfigOperator { + + private static final Logger log = getLogger(OpticalPortOperator.class); + + private OpticalPortOperator() { + } + + /** + * Generates a PortDescription containing fields from a PortDescription and + * an OpticalPortConfig. + * + * @param opc the port config entity from network config + * @param descr a PortDescription + * @return PortDescription based on both sources + */ + public static PortDescription combine(OpticalPortConfig opc, PortDescription descr) { + if (opc == null) { + return descr; + } + + PortNumber port = descr.portNumber(); + final String name = opc.name(); + final String numName = opc.numberName(); + // if the description is null, or the current description port name != config name, + // create a new PortNumber. + PortNumber newPort = null; + if (port == null) { + // try to get the portNumber from the numName. + if (!numName.isEmpty()) { + final long pn = Long.parseLong(numName); + newPort = (!name.isEmpty()) ? PortNumber.portNumber(pn, name) : PortNumber.portNumber(pn); + } else { + // we don't have defining info (a port number value) + throw new RuntimeException("Possible misconfig, bailing on handling for: \n\t" + descr); + } + } else if ((!name.isEmpty()) && !name.equals(port.name())) { + final long pn = (numName.isEmpty()) ? port.toLong() : Long.parseLong(numName); + newPort = PortNumber.portNumber(pn, name); + } + + // Port type won't change unless we're overwriting a port completely. + // Watch out for overwrites to avoid class cast craziness. + boolean noOwrite = opc.type() == descr.type(); + + SparseAnnotations sa = combine(opc, descr.annotations()); + if (noOwrite) { + return updateDescription((newPort == null) ? port : newPort, sa, descr); + } else { + // TODO: must reconstruct a different type of PortDescription. + log.info("Type rewrite from {} to {} required", descr.type(), opc.type()); + } + return descr; + } + + // updates a port description whose port type has not changed. + private static PortDescription updateDescription( + PortNumber port, SparseAnnotations sa, PortDescription descr) { + switch (descr.type()) { + case OMS: + OmsPortDescription oms = (OmsPortDescription) descr; + return new OmsPortDescription(port, oms.isEnabled(), oms.minFrequency(), + oms.maxFrequency(), oms.grid(), sa); + case OCH: + // We might need to update lambda below with STATIC_LAMBDA. + OchPortDescription och = (OchPortDescription) descr; + return new OchPortDescription(port, och.isEnabled(), och.signalType(), + och.isTunable(), och.lambda(), sa); + case ODUCLT: + OduCltPortDescription odu = (OduCltPortDescription) descr; + return new OduCltPortDescription(port, odu.isEnabled(), odu.signalType(), sa); + case PACKET: + case FIBER: + return new DefaultPortDescription(port, descr.isEnabled(), descr.type(), + descr.portSpeed(), sa); + default: + // this includes copper ports. + log.warn("Unsupported optical port type {} - can't update", descr.type()); + return descr; + } + } + + /** + * Generates an annotation from an existing annotation and OptcalPortConfig. + * + * @param opc the port config entity from network config + * @param an the annotation + * @return annotation combining both sources + */ + public static SparseAnnotations combine(OpticalPortConfig opc, SparseAnnotations an) { + DefaultAnnotations.Builder b = DefaultAnnotations.builder(); + if (!opc.staticPort().isEmpty()) { + b.set(AnnotationKeys.STATIC_PORT, opc.staticPort()); + } + if (opc.staticLambda().isPresent()) { + b.set(AnnotationKeys.STATIC_LAMBDA, String.valueOf(opc.staticLambda().get())); + } + // The following may not need to be carried. + if (!opc.name().isEmpty()) { + b.set(AnnotationKeys.PORT_NAME, opc.name()); + } + return DefaultAnnotations.union(an, b.build()); + } + + /** + * Returns a description built from an existing port. + * + * @param port the device port + * @return a PortDescription based on the port + */ + public static PortDescription descriptionOf(Port port) { + checkNotNull(port, "Must supply non-null Port"); + final PortNumber ptn = port.number(); + final boolean isup = port.isEnabled(); + final SparseAnnotations an = (SparseAnnotations) port.annotations(); + switch (port.type()) { + case OMS: + OmsPort oms = (OmsPort) port; + return new OmsPortDescription(ptn, isup, oms.minFrequency(), + oms.maxFrequency(), oms.grid(), an); + case OCH: + OchPort och = (OchPort) port; + return new OchPortDescription(ptn, isup, och.signalType(), + och.isTunable(), och.lambda(), an); + case ODUCLT: + OduCltPort odu = (OduCltPort) port; + return new OduCltPortDescription(ptn, isup, odu.signalType(), an); + default: + return new DefaultPortDescription(ptn, isup, port.type(), port.portSpeed(), an); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/package-info.java new file mode 100644 index 00000000..23c21378 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for tracking global inventory of infrastructure devices. + */ +package org.onosproject.net.device.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java new file mode 100644 index 00000000..53bf30a1 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java @@ -0,0 +1,188 @@ +/* + * Copyright 2015 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.net.driver.impl; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.Behaviour; +import org.onosproject.net.driver.DefaultDriverData; +import org.onosproject.net.driver.DefaultDriverHandler; +import org.onosproject.net.driver.DefaultDriverProvider; +import org.onosproject.net.driver.Driver; +import org.onosproject.net.driver.DriverAdminService; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.onlab.util.Tools.nullIsNotFound; +import static org.onosproject.net.AnnotationKeys.DRIVER; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + + +/** + * Manages inventory of device drivers. + */ +@Component(immediate = true) +@Service +public class DriverManager extends DefaultDriverProvider implements DriverAdminService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private static final String NO_DRIVER = "Driver not found"; + private static final String NO_DEVICE = "Device not found"; + private static final String DEFAULT = "default"; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + private Set<DriverProvider> providers = Sets.newConcurrentHashSet(); + private Map<String, Driver> driverByKey = Maps.newConcurrentMap(); + + @Activate + protected void activate() { + log.info("Started"); + } + + @Deactivate + protected void deactivate() { + log.info("Stopped"); + } + + + @Override + public Set<DriverProvider> getProviders() { + return ImmutableSet.copyOf(providers); + } + + @Override + public void registerProvider(DriverProvider provider) { + provider.getDrivers().forEach(driver -> { + addDrivers(provider.getDrivers()); + driverByKey.put(key(driver.manufacturer(), + driver.hwVersion(), + driver.swVersion()), driver); + }); + providers.add(provider); + } + + @Override + public void unregisterProvider(DriverProvider provider) { + provider.getDrivers().forEach(driver -> { + removeDrivers(provider.getDrivers()); + driverByKey.remove(key(driver.manufacturer(), + driver.hwVersion(), + driver.swVersion())); + }); + providers.remove(provider); + } + + @Override + public Set<Driver> getDrivers() { + checkPermission(DRIVER_READ); + + ImmutableSet.Builder<Driver> builder = ImmutableSet.builder(); + drivers.values().forEach(builder::add); + return builder.build(); + } + + @Override + public Set<Driver> getDrivers(Class<? extends Behaviour> withBehaviour) { + checkPermission(DRIVER_READ); + + return drivers.values().stream() + .filter(d -> d.hasBehaviour(withBehaviour)) + .collect(Collectors.toSet()); + } + + @Override + public Driver getDriver(String driverName) { + checkPermission(DRIVER_READ); + + return nullIsNotFound(drivers.get(driverName), NO_DRIVER); + } + + @Override + public Driver getDriver(String mfr, String hw, String sw) { + checkPermission(DRIVER_READ); + + // First attempt a literal search. + Driver driver = driverByKey.get(key(mfr, hw, sw)); + if (driver != null) { + return driver; + } + + // Otherwise, sweep through the key space and attempt to match using + // regular expression matching. + Optional<Driver> optional = driverByKey.values().stream() + .filter(d -> matches(d, mfr, hw, sw)).findFirst(); + + // If no matching driver is found, return default. + return optional.isPresent() ? optional.get() : drivers.get(DEFAULT); + } + + // Matches the given driver using ERE matching against the given criteria. + private boolean matches(Driver d, String mfr, String hw, String sw) { + // TODO: consider pre-compiling the expressions in the future + return mfr.matches(d.manufacturer()) && + hw.matches(d.hwVersion()) && + sw.matches(d.swVersion()); + } + + @Override + public Driver getDriver(DeviceId deviceId) { + checkPermission(DRIVER_READ); + + Device device = nullIsNotFound(deviceService.getDevice(deviceId), NO_DEVICE); + String driverName = device.annotations().value(DRIVER); + if (driverName != null) { + return getDriver(driverName); + } + return nullIsNotFound(getDriver(device.manufacturer(), + device.hwVersion(), device.swVersion()), + NO_DRIVER); + } + + @Override + public DriverHandler createHandler(DeviceId deviceId, String... credentials) { + checkPermission(DRIVER_WRITE); + + Driver driver = getDriver(deviceId); + return new DefaultDriverHandler(new DefaultDriverData(driver, deviceId)); + } + + // Produces a composite driver key using the specified components. + private String key(String mfr, String hw, String sw) { + return String.format("%s-%s-%s", mfr, hw, sw); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/package-info.java new file mode 100644 index 00000000..2006391a --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementation of the device driver management subsystem. + */ +package org.onosproject.net.driver.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/EdgeManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/EdgeManager.java new file mode 100644 index 00000000..e992f7a4 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/EdgeManager.java @@ -0,0 +1,241 @@ +/* + * Copyright 2014 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.net.edgeservice.impl; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.event.AbstractListenerManager; +import org.onosproject.event.Event; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.edge.EdgePortEvent; +import org.onosproject.net.edge.EdgePortListener; +import org.onosproject.net.edge.EdgePortService; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyListener; +import org.onosproject.net.topology.TopologyService; +import org.slf4j.Logger; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.onosproject.net.device.DeviceEvent.Type.*; +import static org.onosproject.net.edge.EdgePortEvent.Type.EDGE_PORT_ADDED; +import static org.onosproject.net.edge.EdgePortEvent.Type.EDGE_PORT_REMOVED; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * This is an implementation of the edge net service. + */ +@Component(immediate = true) +@Service +public class EdgeManager + extends AbstractListenerManager<EdgePortEvent, EdgePortListener> + implements EdgePortService { + + private final Logger log = getLogger(getClass()); + + private Topology topology; + + private final Map<DeviceId, Set<ConnectPoint>> connectionPoints = Maps.newConcurrentMap(); + + private final TopologyListener topologyListener = new InnerTopologyListener(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Activate + public void activate() { + eventDispatcher.addSink(EdgePortEvent.class, listenerRegistry); + topologyService.addListener(topologyListener); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + eventDispatcher.removeSink(EdgePortEvent.class); + topologyService.removeListener(topologyListener); + log.info("Stopped"); + } + + @Override + public boolean isEdgePoint(ConnectPoint point) { + return !topologyService.isInfrastructure(topologyService.currentTopology(), point); + } + + @Override + public Iterable<ConnectPoint> getEdgePoints() { + ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder(); + connectionPoints.forEach((k, v) -> v.forEach(builder::add)); + return builder.build(); + } + + @Override + public Iterable<ConnectPoint> getEdgePoints(DeviceId deviceId) { + ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder(); + Set<ConnectPoint> set = connectionPoints.get(deviceId); + if (set != null) { + set.forEach(builder::add); + } + return builder.build(); + } + + @Override + public void emitPacket(ByteBuffer data, Optional<TrafficTreatment> treatment) { + TrafficTreatment.Builder builder = treatment.isPresent() ? + DefaultTrafficTreatment.builder(treatment.get()) : + DefaultTrafficTreatment.builder(); + getEdgePoints().forEach(p -> packetService.emit(packet(builder, p, data))); + } + + @Override + public void emitPacket(DeviceId deviceId, ByteBuffer data, + Optional<TrafficTreatment> treatment) { + TrafficTreatment.Builder builder = treatment.isPresent() ? + DefaultTrafficTreatment.builder(treatment.get()) : + DefaultTrafficTreatment.builder(); + getEdgePoints(deviceId).forEach(p -> packetService.emit(packet(builder, p, data))); + } + + private OutboundPacket packet(TrafficTreatment.Builder builder, ConnectPoint point, ByteBuffer data) { + builder.setOutput(point.port()); + return new DefaultOutboundPacket(point.deviceId(), builder.build(), data); + } + + // Internal listener for topo events used to keep our edge-port cache + // up to date. + private class InnerTopologyListener implements TopologyListener { + @Override + public void event(TopologyEvent event) { + topology = event.subject(); + List<Event> triggers = event.reasons(); + if (triggers != null) { + triggers.forEach(reason -> { + if (reason instanceof DeviceEvent) { + processDeviceEvent((DeviceEvent) reason); + } else if (reason instanceof LinkEvent) { + processLinkEvent((LinkEvent) reason); + } + }); + } else { + //FIXME special case of preexisting edgeport & no triggerless events could cause this to never hit and + //never discover an edgeport that should have been discovered. + loadAllEdgePorts(); + } + } + } + + // Initial loading of the edge port cache. + private void loadAllEdgePorts() { + deviceService.getAvailableDevices().forEach(d -> deviceService.getPorts(d.id()) + .forEach(p -> addEdgePort(new ConnectPoint(d.id(), p.number())))); + } + + // Processes a link event by adding or removing its end-points in our cache. + private void processLinkEvent(LinkEvent event) { + if (event.type() == LinkEvent.Type.LINK_ADDED) { + removeEdgePort(event.subject().src()); + removeEdgePort(event.subject().dst()); + } else if (event.type() == LinkEvent.Type.LINK_REMOVED) { + addEdgePort(event.subject().src()); + addEdgePort(event.subject().dst()); + } + } + + // Processes a device event by adding or removing its end-points in our cache. + private void processDeviceEvent(DeviceEvent event) { + //FIXME handle the case where a device is suspended, this may or may not come up + DeviceEvent.Type type = event.type(); + DeviceId id = event.subject().id(); + + if (type == DEVICE_ADDED || + type == DEVICE_AVAILABILITY_CHANGED && deviceService.isAvailable(id)) { + // When device is added or becomes available, add all its ports + deviceService.getPorts(event.subject().id()) + .forEach(p -> addEdgePort(new ConnectPoint(id, p.number()))); + } else if (type == DEVICE_REMOVED || + type == DEVICE_AVAILABILITY_CHANGED && !deviceService.isAvailable(id)) { + // When device is removed or becomes unavailable, remove all its ports + deviceService.getPorts(event.subject().id()) + .forEach(p -> removeEdgePort(new ConnectPoint(id, p.number()))); + connectionPoints.remove(id); + + } else if (type == DeviceEvent.Type.PORT_ADDED || + type == PORT_UPDATED && event.port().isEnabled()) { + addEdgePort(new ConnectPoint(id, event.port().number())); + } else if (type == DeviceEvent.Type.PORT_REMOVED || + type == PORT_UPDATED && !event.port().isEnabled()) { + removeEdgePort(new ConnectPoint(id, event.port().number())); + } + } + + // Adds the specified connection point to the edge points if needed. + private void addEdgePort(ConnectPoint point) { + if (!topologyService.isInfrastructure(topology, point) && !point.port().isLogical()) { + Set<ConnectPoint> set = connectionPoints.get(point.deviceId()); + if (set == null) { + set = Sets.newConcurrentHashSet(); + connectionPoints.put(point.deviceId(), set); + } + if (set.add(point)) { + post(new EdgePortEvent(EDGE_PORT_ADDED, point)); + } + } + } + + // Removes the specified connection point from the edge points. + private void removeEdgePort(ConnectPoint point) { + if (!point.port().isLogical()) { + Set<ConnectPoint> set = connectionPoints.get(point.deviceId()); + if (set == null) { + return; + } + if (set.remove(point)) { + post(new EdgePortEvent(EDGE_PORT_REMOVED, point)); + } + if (set.isEmpty()) { + connectionPoints.remove(point.deviceId()); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/package-info.java new file mode 100644 index 00000000..fd867326 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Core subsystem for interacting with network edges. + */ +package org.onosproject.net.edgeservice.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java new file mode 100644 index 00000000..a1d046c5 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java @@ -0,0 +1,593 @@ +/* + * Copyright 2014-2015 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.net.flow.impl; + +import com.google.common.base.Strings; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.Tools; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.CompletedBatchOperation; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleBatchEntry; +import org.onosproject.net.flow.FlowRuleBatchEvent; +import org.onosproject.net.flow.FlowRuleBatchOperation; +import org.onosproject.net.flow.FlowRuleBatchRequest; +import org.onosproject.net.flow.FlowRuleEvent; +import org.onosproject.net.flow.FlowRuleListener; +import org.onosproject.net.flow.FlowRuleOperation; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleProvider; +import org.onosproject.net.flow.FlowRuleProviderRegistry; +import org.onosproject.net.flow.FlowRuleProviderService; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.FlowRuleStore; +import org.onosproject.net.flow.FlowRuleStoreDelegate; +import org.onosproject.net.provider.AbstractProviderService; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + + +/** + * Provides implementation of the flow NB & SB APIs. + */ +@Component(immediate = true, enabled = true) +@Service +public class FlowRuleManager + extends AbstractListenerProviderRegistry<FlowRuleEvent, FlowRuleListener, + FlowRuleProvider, FlowRuleProviderService> + implements FlowRuleService, FlowRuleProviderRegistry { + + public static final String FLOW_RULE_NULL = "FlowRule cannot be null"; + private static final boolean ALLOW_EXTRANEOUS_RULES = false; + + @Property(name = "allowExtraneousRules", boolValue = ALLOW_EXTRANEOUS_RULES, + label = "Allow flow rules in switch not installed by ONOS") + private boolean allowExtraneousRules = ALLOW_EXTRANEOUS_RULES; + + private final Logger log = getLogger(getClass()); + + private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate(); + + protected ExecutorService deviceInstallers = + Executors.newFixedThreadPool(32, groupedThreads("onos/flowservice", "device-installer-%d")); + + protected ExecutorService operationsService = + Executors.newFixedThreadPool(32, groupedThreads("onos/flowservice", "operations-%d")); + + private IdGenerator idGenerator; + + private Map<Long, FlowOperationsProcessor> pendingFlowOperations + = new ConcurrentHashMap<>(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + idGenerator = coreService.getIdGenerator(FLOW_OP_TOPIC); + + modified(context); + + store.setDelegate(delegate); + eventDispatcher.addSink(FlowRuleEvent.class, listenerRegistry); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + deviceInstallers.shutdownNow(); + operationsService.shutdownNow(); + store.unsetDelegate(delegate); + eventDispatcher.removeSink(FlowRuleEvent.class); + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + return; + } + + Dictionary<?, ?> properties = context.getProperties(); + + String s = Tools.get(properties, "allowExtraneousRules"); + allowExtraneousRules = Strings.isNullOrEmpty(s) ? ALLOW_EXTRANEOUS_RULES : Boolean.valueOf(s); + + if (allowExtraneousRules) { + log.info("Allowing flow rules not installed by ONOS"); + } + } + + @Override + public int getFlowRuleCount() { + checkPermission(FLOWRULE_READ); + return store.getFlowRuleCount(); + } + + @Override + public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) { + checkPermission(FLOWRULE_READ); + return store.getFlowEntries(deviceId); + } + + @Override + public void applyFlowRules(FlowRule... flowRules) { + checkPermission(FLOWRULE_WRITE); + + FlowRuleOperations.Builder builder = FlowRuleOperations.builder(); + for (int i = 0; i < flowRules.length; i++) { + builder.add(flowRules[i]); + } + apply(builder.build()); + } + + @Override + public void removeFlowRules(FlowRule... flowRules) { + checkPermission(FLOWRULE_WRITE); + + FlowRuleOperations.Builder builder = FlowRuleOperations.builder(); + for (int i = 0; i < flowRules.length; i++) { + builder.remove(flowRules[i]); + } + apply(builder.build()); + } + + @Override + public void removeFlowRulesById(ApplicationId id) { + checkPermission(FLOWRULE_WRITE); + removeFlowRules(Iterables.toArray(getFlowRulesById(id), FlowRule.class)); + } + + @Override + public Iterable<FlowRule> getFlowRulesById(ApplicationId id) { + checkPermission(FLOWRULE_READ); + + Set<FlowRule> flowEntries = Sets.newHashSet(); + for (Device d : deviceService.getDevices()) { + for (FlowEntry flowEntry : store.getFlowEntries(d.id())) { + if (flowEntry.appId() == id.id()) { + flowEntries.add(flowEntry); + } + } + } + return flowEntries; + } + + @Override + public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) { + checkPermission(FLOWRULE_READ); + + Set<FlowRule> matches = Sets.newHashSet(); + long toLookUp = ((long) appId.id() << 16) | groupId; + for (Device d : deviceService.getDevices()) { + for (FlowEntry flowEntry : store.getFlowEntries(d.id())) { + if ((flowEntry.id().value() >>> 32) == toLookUp) { + matches.add(flowEntry); + } + } + } + return matches; + } + + @Override + public void apply(FlowRuleOperations ops) { + checkPermission(FLOWRULE_WRITE); + operationsService.submit(new FlowOperationsProcessor(ops)); + } + + @Override + protected FlowRuleProviderService createProviderService( + FlowRuleProvider provider) { + return new InternalFlowRuleProviderService(provider); + } + + private class InternalFlowRuleProviderService + extends AbstractProviderService<FlowRuleProvider> + implements FlowRuleProviderService { + + final Map<FlowEntry, Long> lastSeen = Maps.newConcurrentMap(); + + protected InternalFlowRuleProviderService(FlowRuleProvider provider) { + super(provider); + } + + @Override + public void flowRemoved(FlowEntry flowEntry) { + checkNotNull(flowEntry, FLOW_RULE_NULL); + checkValidity(); + lastSeen.remove(flowEntry); + FlowEntry stored = store.getFlowEntry(flowEntry); + if (stored == null) { + log.debug("Rule already evicted from store: {}", flowEntry); + return; + } + Device device = deviceService.getDevice(flowEntry.deviceId()); + FlowRuleProvider frp = getProvider(device.providerId()); + FlowRuleEvent event = null; + switch (stored.state()) { + case ADDED: + case PENDING_ADD: + frp.applyFlowRule(stored); + break; + case PENDING_REMOVE: + case REMOVED: + event = store.removeFlowRule(stored); + break; + default: + break; + + } + if (event != null) { + log.debug("Flow {} removed", flowEntry); + post(event); + } + } + + + private void flowMissing(FlowEntry flowRule) { + checkNotNull(flowRule, FLOW_RULE_NULL); + checkValidity(); + Device device = deviceService.getDevice(flowRule.deviceId()); + FlowRuleProvider frp = getProvider(device.providerId()); + FlowRuleEvent event = null; + switch (flowRule.state()) { + case PENDING_REMOVE: + case REMOVED: + event = store.removeFlowRule(flowRule); + frp.removeFlowRule(flowRule); + break; + case ADDED: + case PENDING_ADD: + try { + frp.applyFlowRule(flowRule); + } catch (UnsupportedOperationException e) { + log.warn(e.getMessage()); + if (flowRule instanceof DefaultFlowEntry) { + ((DefaultFlowEntry) flowRule).setState(FlowEntry.FlowEntryState.FAILED); + } + } + break; + default: + log.debug("Flow {} has not been installed.", flowRule); + } + + if (event != null) { + log.debug("Flow {} removed", flowRule); + post(event); + } + + } + + + private void extraneousFlow(FlowRule flowRule) { + checkNotNull(flowRule, FLOW_RULE_NULL); + checkValidity(); + FlowRuleProvider frp = getProvider(flowRule.deviceId()); + frp.removeFlowRule(flowRule); + log.debug("Flow {} is on switch but not in store.", flowRule); + } + + + private void flowAdded(FlowEntry flowEntry) { + checkNotNull(flowEntry, FLOW_RULE_NULL); + checkValidity(); + + if (checkRuleLiveness(flowEntry, store.getFlowEntry(flowEntry))) { + + FlowRuleEvent event = store.addOrUpdateFlowRule(flowEntry); + if (event == null) { + log.debug("No flow store event generated."); + } else { + log.trace("Flow {} {}", flowEntry, event.type()); + post(event); + } + } else { + log.debug("Removing flow rules...."); + removeFlowRules(flowEntry); + } + + } + + private boolean checkRuleLiveness(FlowEntry swRule, FlowEntry storedRule) { + if (storedRule == null) { + return false; + } + if (storedRule.isPermanent()) { + return true; + } + + final long timeout = storedRule.timeout() * 1000; + final long currentTime = System.currentTimeMillis(); + if (storedRule.packets() != swRule.packets()) { + lastSeen.put(storedRule, currentTime); + return true; + } + if (!lastSeen.containsKey(storedRule)) { + // checking for the first time + lastSeen.put(storedRule, storedRule.lastSeen()); + // Use following if lastSeen attr. was removed. + //lastSeen.put(storedRule, currentTime); + } + Long last = lastSeen.get(storedRule); + if (last == null) { + // concurrently removed? let the liveness check fail + return false; + } + + if ((currentTime - last) <= timeout) { + return true; + } + return false; + } + + @Override + public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) { + Map<FlowEntry, FlowEntry> storedRules = Maps.newHashMap(); + store.getFlowEntries(deviceId).forEach(f -> storedRules.put(f, f)); + + for (FlowEntry rule : flowEntries) { + try { + FlowEntry storedRule = storedRules.remove(rule); + if (storedRule != null) { + if (storedRule.exactMatch(rule)) { + // we both have the rule, let's update some info then. + flowAdded(rule); + } else { + // the two rules are not an exact match - remove the + // switch's rule and install our rule + extraneousFlow(rule); + flowMissing(storedRule); + } + } else { + // the device has a rule the store does not have + if (!allowExtraneousRules) { + extraneousFlow(rule); + } + } + } catch (Exception e) { + log.debug("Can't process added or extra rule {}", e.getMessage()); + continue; + } + } + for (FlowEntry rule : storedRules.keySet()) { + try { + // there are rules in the store that aren't on the switch + log.debug("Adding rule in store, but not on switch {}", rule); + flowMissing(rule); + } catch (Exception e) { + log.debug("Can't add missing flow rule {}", e.getMessage()); + continue; + } + } + + } + + @Override + public void batchOperationCompleted(long batchId, CompletedBatchOperation operation) { + store.batchOperationComplete(FlowRuleBatchEvent.completed( + new FlowRuleBatchRequest(batchId, Collections.emptySet()), + operation + )); + } + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements FlowRuleStoreDelegate { + + + // TODO: Right now we only dispatch events at individual flowEntry level. + // It may be more efficient for also dispatch events as a batch. + @Override + public void notify(FlowRuleBatchEvent event) { + final FlowRuleBatchRequest request = event.subject(); + switch (event.type()) { + case BATCH_OPERATION_REQUESTED: + // Request has been forwarded to MASTER Node, and was + request.ops().stream().forEach( + op -> { + switch (op.operator()) { + + case ADD: + post(new FlowRuleEvent(RULE_ADD_REQUESTED, + op.target())); + break; + case REMOVE: + post(new FlowRuleEvent(RULE_REMOVE_REQUESTED, + op.target())); + break; + case MODIFY: + //TODO: do something here when the time comes. + break; + default: + log.warn("Unknown flow operation operator: {}", op.operator()); + } + } + ); + + DeviceId deviceId = event.deviceId(); + + FlowRuleBatchOperation batchOperation = + request.asBatchOperation(deviceId); + + FlowRuleProvider flowRuleProvider = getProvider(deviceId); + if (flowRuleProvider != null) { + flowRuleProvider.executeBatch(batchOperation); + } + + break; + + case BATCH_OPERATION_COMPLETED: + + FlowOperationsProcessor fops = pendingFlowOperations.remove( + event.subject().batchId()); + if (event.result().isSuccess()) { + if (fops != null) { + fops.satisfy(event.deviceId()); + } + } else { + fops.fail(event.deviceId(), event.result().failedItems()); + } + + break; + + default: + break; + } + } + } + + private class FlowOperationsProcessor implements Runnable { + + private final List<Set<FlowRuleOperation>> stages; + private final FlowRuleOperationsContext context; + private final FlowRuleOperations fops; + private final AtomicBoolean hasFailed = new AtomicBoolean(false); + + private Set<DeviceId> pendingDevices; + + public FlowOperationsProcessor(FlowRuleOperations ops) { + this.stages = Lists.newArrayList(ops.stages()); + this.context = ops.callback(); + this.fops = ops; + pendingDevices = Sets.newConcurrentHashSet(); + } + + @Override + public void run() { + if (stages.size() > 0) { + process(stages.remove(0)); + } else if (!hasFailed.get() && context != null) { + context.onSuccess(fops); + } + } + + private void process(Set<FlowRuleOperation> ops) { + Multimap<DeviceId, FlowRuleBatchEntry> perDeviceBatches = + ArrayListMultimap.create(); + + FlowRuleBatchEntry fbe; + for (FlowRuleOperation flowRuleOperation : ops) { + switch (flowRuleOperation.type()) { + // FIXME: Brian needs imagination when creating class names. + case ADD: + fbe = new FlowRuleBatchEntry( + FlowRuleBatchEntry.FlowRuleOperation.ADD, flowRuleOperation.rule()); + break; + case MODIFY: + fbe = new FlowRuleBatchEntry( + FlowRuleBatchEntry.FlowRuleOperation.MODIFY, flowRuleOperation.rule()); + break; + case REMOVE: + fbe = new FlowRuleBatchEntry( + FlowRuleBatchEntry.FlowRuleOperation.REMOVE, flowRuleOperation.rule()); + break; + default: + throw new UnsupportedOperationException("Unknown flow rule type " + flowRuleOperation.type()); + } + pendingDevices.add(flowRuleOperation.rule().deviceId()); + perDeviceBatches.put(flowRuleOperation.rule().deviceId(), fbe); + } + + + for (DeviceId deviceId : perDeviceBatches.keySet()) { + long id = idGenerator.getNewId(); + final FlowRuleBatchOperation b = new FlowRuleBatchOperation(perDeviceBatches.get(deviceId), + deviceId, id); + pendingFlowOperations.put(id, this); + deviceInstallers.submit(() -> store.storeBatch(b)); + } + } + + public void satisfy(DeviceId devId) { + pendingDevices.remove(devId); + if (pendingDevices.isEmpty()) { + operationsService.submit(this); + } + } + + + + public void fail(DeviceId devId, Set<? extends FlowRule> failures) { + hasFailed.set(true); + pendingDevices.remove(devId); + if (pendingDevices.isEmpty()) { + operationsService.submit(this); + } + + if (context != null) { + final FlowRuleOperations.Builder failedOpsBuilder = + FlowRuleOperations.builder(); + failures.stream().forEach(failedOpsBuilder::add); + + context.onError(failedOpsBuilder.build()); + } + } + + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/package-info.java new file mode 100644 index 00000000..69934b6f --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for tracking and manipulating global flow state. + */ +package org.onosproject.net.flow.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java new file mode 100644 index 00000000..a76a298f --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java @@ -0,0 +1,416 @@ +/* + * Copyright 2015 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.net.flowobjective.impl; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.osgi.DefaultServiceDirectory; +import org.onlab.osgi.ServiceDirectory; +import org.onlab.util.ItemNotFoundException; +import org.onosproject.cluster.ClusterService; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.DefaultDriverProviderService; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverService; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.flowobjective.ObjectiveEvent; +import org.onosproject.net.group.GroupService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + + +/** + * Provides implementation of the flow objective programming service. + */ +@Component(immediate = true) +@Service +public class FlowObjectiveManager implements FlowObjectiveService { + + public static final int INSTALL_RETRY_ATTEMPTS = 5; + public static final long INSTALL_RETRY_INTERVAL = 1000; // ms + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DriverService driverService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + // Note: The following dependencies are added on behalf of the pipeline + // driver behaviours to assure these services are available for their + // initialization. + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowRuleService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected GroupService groupService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowObjectiveStore flowObjectiveStore; + + // Note: This must remain an optional dependency to allow re-install of default drivers. + // Note: For now disabled until we can move to OPTIONAL_UNARY dependency + // @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC) + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DefaultDriverProviderService defaultDriverService; + + private final FlowObjectiveStoreDelegate delegate = new InternalStoreDelegate(); + + private final Map<DeviceId, DriverHandler> driverHandlers = Maps.newConcurrentMap(); + private final Map<DeviceId, Pipeliner> pipeliners = Maps.newConcurrentMap(); + + private final PipelinerContext context = new InnerPipelineContext(); + private final MastershipListener mastershipListener = new InnerMastershipListener(); + private final DeviceListener deviceListener = new InnerDeviceListener(); + + protected ServiceDirectory serviceDirectory = new DefaultServiceDirectory(); + + private Map<Integer, Set<PendingNext>> pendingForwards = Maps.newConcurrentMap(); + + private ExecutorService executorService; + + @Activate + protected void activate() { + executorService = newFixedThreadPool(4, groupedThreads("onos/objective-installer", "%d")); + flowObjectiveStore.setDelegate(delegate); + mastershipService.addListener(mastershipListener); + deviceService.addListener(deviceListener); + deviceService.getDevices().forEach(device -> setupPipelineHandler(device.id())); + log.info("Started"); + } + + @Deactivate + protected void deactivate() { + flowObjectiveStore.unsetDelegate(delegate); + mastershipService.removeListener(mastershipListener); + deviceService.removeListener(deviceListener); + executorService.shutdown(); + pipeliners.clear(); + driverHandlers.clear(); + log.info("Stopped"); + } + + /** + * Task that passes the flow objective down to the driver. The task will + * make a few attempts to find the appropriate driver, then eventually give + * up and report an error if no suitable driver could be found. + */ + private class ObjectiveInstaller implements Runnable { + private final DeviceId deviceId; + private final Objective objective; + + private final int numAttempts; + + public ObjectiveInstaller(DeviceId deviceId, Objective objective) { + this(deviceId, objective, 1); + } + + public ObjectiveInstaller(DeviceId deviceId, Objective objective, int attemps) { + this.deviceId = checkNotNull(deviceId); + this.objective = checkNotNull(objective); + this.numAttempts = checkNotNull(attemps); + } + + @Override + public void run() { + try { + Pipeliner pipeliner = getDevicePipeliner(deviceId); + + if (pipeliner != null) { + if (objective instanceof NextObjective) { + pipeliner.next((NextObjective) objective); + } else if (objective instanceof ForwardingObjective) { + pipeliner.forward((ForwardingObjective) objective); + } else { + pipeliner.filter((FilteringObjective) objective); + } + } else if (numAttempts < INSTALL_RETRY_ATTEMPTS) { + Thread.sleep(INSTALL_RETRY_INTERVAL); + executorService.submit(new ObjectiveInstaller(deviceId, objective, numAttempts + 1)); + } else { + // Otherwise we've tried a few times and failed, report an + // error back to the user. + objective.context().ifPresent( + c -> c.onError(objective, ObjectiveError.DEVICEMISSING)); + } + } catch (Exception e) { + log.warn("Exception while installing flow objective", e); + } + } + } + + @Override + public void filter(DeviceId deviceId, FilteringObjective filteringObjective) { + checkPermission(FLOWRULE_WRITE); + executorService.submit(new ObjectiveInstaller(deviceId, filteringObjective)); + } + + @Override + public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) { + checkPermission(FLOWRULE_WRITE); + if (queueObjective(deviceId, forwardingObjective)) { + return; + } + executorService.submit(new ObjectiveInstaller(deviceId, forwardingObjective)); + } + + @Override + public void next(DeviceId deviceId, NextObjective nextObjective) { + checkPermission(FLOWRULE_WRITE); + executorService.submit(new ObjectiveInstaller(deviceId, nextObjective)); + } + + @Override + public int allocateNextId() { + checkPermission(FLOWRULE_WRITE); + return flowObjectiveStore.allocateNextId(); + } + + @Override + public void initPolicy(String policy) {} + + private boolean queueObjective(DeviceId deviceId, ForwardingObjective fwd) { + if (fwd.nextId() != null && + flowObjectiveStore.getNextGroup(fwd.nextId()) == null) { + log.trace("Queuing forwarding objective for nextId {}", fwd.nextId()); + if (pendingForwards.putIfAbsent(fwd.nextId(), + Sets.newHashSet(new PendingNext(deviceId, fwd))) != null) { + Set<PendingNext> pending = pendingForwards.get(fwd.nextId()); + pending.add(new PendingNext(deviceId, fwd)); + } + return true; + } + return false; + } + + // Retrieves the device pipeline behaviour from the cache. + private Pipeliner getDevicePipeliner(DeviceId deviceId) { + return pipeliners.get(deviceId); + } + + private void setupPipelineHandler(DeviceId deviceId) { + if (defaultDriverService == null) { + // We're not ready to go to work yet. + return; + } + + // Attempt to lookup the handler in the cache + DriverHandler handler = driverHandlers.get(deviceId); + cTime = now(); + + if (handler == null) { + try { + // Otherwise create it and if it has pipeline behaviour, cache it + handler = driverService.createHandler(deviceId); + dTime = now(); + if (!handler.driver().hasBehaviour(Pipeliner.class)) { + log.warn("Pipeline behaviour not supported for device {}", + deviceId); + return; + } + } catch (ItemNotFoundException e) { + log.warn("No applicable driver for device {}", deviceId); + return; + } + + driverHandlers.put(deviceId, handler); + eTime = now(); + } + + // Always (re)initialize the pipeline behaviour + log.info("Driver {} bound to device {} ... initializing driver", + handler.driver().name(), deviceId); + hTime = now(); + Pipeliner pipeliner = handler.behaviour(Pipeliner.class); + hbTime = now(); + pipeliner.init(deviceId, context); + pipeliners.putIfAbsent(deviceId, pipeliner); + } + + // Triggers driver setup when the local node becomes a device master. + private class InnerMastershipListener implements MastershipListener { + @Override + public void event(MastershipEvent event) { + switch (event.type()) { + case MASTER_CHANGED: + log.debug("mastership changed on device {}", event.subject()); + start = now(); + if (deviceService.isAvailable(event.subject())) { + setupPipelineHandler(event.subject()); + } + stopWatch(); + break; + case BACKUPS_CHANGED: + break; + default: + break; + } + } + } + + // Triggers driver setup when a device is (re)detected. + private class InnerDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + log.debug("Device either added or availability changed {}", + event.subject().id()); + start = now(); + if (deviceService.isAvailable(event.subject().id())) { + log.debug("Device is now available {}", event.subject().id()); + setupPipelineHandler(event.subject().id()); + } + stopWatch(); + break; + case DEVICE_UPDATED: + break; + case DEVICE_REMOVED: + break; + case DEVICE_SUSPENDED: + break; + case PORT_ADDED: + break; + case PORT_UPDATED: + break; + case PORT_REMOVED: + break; + default: + break; + } + } + } + + // Temporary mechanism to monitor pipeliner setup time-cost; there are + // intermittent time where this takes in excess of 2 seconds. Why? + private long start = 0, totals = 0, count = 0; + private long cTime, dTime, eTime, hTime, hbTime; + private static final long LIMIT = 500; + + private long now() { + return System.currentTimeMillis(); + } + + private void stopWatch() { + long duration = System.currentTimeMillis() - start; + totals += duration; + count += 1; + if (duration > LIMIT) { + log.info("Pipeline setup took {} ms; avg {} ms; cTime={}, dTime={}, eTime={}, hTime={}, hbTime={}", + duration, totals / count, diff(cTime), diff(dTime), diff(eTime), diff(hTime), diff(hbTime)); + } + } + + private long diff(long bTime) { + long diff = bTime - start; + return diff < 0 ? 0 : diff; + } + + // Processing context for initializing pipeline driver behaviours. + private class InnerPipelineContext implements PipelinerContext { + @Override + public ServiceDirectory directory() { + return serviceDirectory; + } + + @Override + public FlowObjectiveStore store() { + return flowObjectiveStore; + } + } + + private class InternalStoreDelegate implements FlowObjectiveStoreDelegate { + @Override + public void notify(ObjectiveEvent event) { + log.debug("Received notification of obj event {}", event); + Set<PendingNext> pending = pendingForwards.remove(event.subject()); + + if (pending == null) { + log.debug("Nothing pending for this obj event"); + return; + } + + log.debug("Processing pending forwarding objectives {}", pending.size()); + + pending.forEach(p -> getDevicePipeliner(p.deviceId()) + .forward(p.forwardingObjective())); + + } + } + + /** + * Data class used to hold a pending forwarding objective that could not + * be processed because the associated next object was not present. + */ + private class PendingNext { + private final DeviceId deviceId; + private final ForwardingObjective fwd; + + public PendingNext(DeviceId deviceId, ForwardingObjective fwd) { + this.deviceId = deviceId; + this.fwd = fwd; + } + + public DeviceId deviceId() { + return deviceId; + } + + public ForwardingObjective forwardingObjective() { + return fwd; + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FilterTable.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FilterTable.java new file mode 100644 index 00000000..b46ce8b3 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FilterTable.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import org.onosproject.net.flowobjective.FilteringObjective; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides a table to store Fitler. + */ +public class FilterTable { + + protected Map<Integer, FilteringObjective> filterMap; + + public FilterTable() { + this.filterMap = new HashMap<>(); + } + + public List<FilteringObjective> updateFilter(FilteringObjective filteringObjective) { + List<FilteringObjective> updates = new ArrayList<>(); + switch (filteringObjective.op()) { + case ADD: + this.filterMap.put(filteringObjective.id(), filteringObjective); + updates.add(filteringObjective); + break; + case REMOVE: + this.filterMap.remove(filteringObjective.id()); + updates.add(filteringObjective); + break; + default: + break; + } + return updates; + } + + public List<FilteringObjective> updateFilter(List<FilteringObjective> filteringObjectives) { + List<FilteringObjective> updates = new ArrayList<>(); + for (FilteringObjective filteringObjective : filteringObjectives) { + updates.addAll(this.updateFilter(filteringObjective)); + } + return updates; + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionManager.java new file mode 100644 index 00000000..3ef98bd8 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionManager.java @@ -0,0 +1,439 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.osgi.DefaultServiceDirectory; +import org.onlab.osgi.ServiceDirectory; +import org.onlab.util.ItemNotFoundException; +import org.onosproject.cluster.ClusterService; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.DefaultDriverProviderService; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverService; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.flowobjective.ObjectiveEvent; +import org.onosproject.net.group.GroupService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Provides implementation of the flow objective programming service with composition feature. + * + * Note: This is an experimental, alternative implementation of the FlowObjectiveManager + * that supports composition. It can be enabled by setting the enable flag below to true, + * and you should also add "enabled = false" to the FlowObjectiveManager. + * + * The implementation relies a FlowObjectiveCompositionTree that is not yet distributed, + * so it will not have high availability and may break if device mastership changes. + * Therefore, it is safest to use this component in a single instance scenario. + * This comment will be removed when a distributed implementation is available. + */ +@Component(immediate = true, enabled = false) +@Service +public class FlowObjectiveCompositionManager implements FlowObjectiveService { + + public enum PolicyOperator { + Parallel, + Sequential, + Override, + Application + } + + public static final int INSTALL_RETRY_ATTEMPTS = 5; + public static final long INSTALL_RETRY_INTERVAL = 1000; // ms + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DriverService driverService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + // Note: The following dependencies are added on behalf of the pipeline + // driver behaviours to assure these services are available for their + // initialization. + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowRuleService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected GroupService groupService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowObjectiveStore flowObjectiveStore; + + // Note: This must remain an optional dependency to allow re-install of default drivers. + // Note: For now disabled until we can move to OPTIONAL_UNARY dependency + // @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC) + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DefaultDriverProviderService defaultDriverService; + + private final FlowObjectiveStoreDelegate delegate = new InternalStoreDelegate(); + + private final Map<DeviceId, DriverHandler> driverHandlers = Maps.newConcurrentMap(); + private final Map<DeviceId, Pipeliner> pipeliners = Maps.newConcurrentMap(); + + private final PipelinerContext context = new InnerPipelineContext(); + private final MastershipListener mastershipListener = new InnerMastershipListener(); + private final DeviceListener deviceListener = new InnerDeviceListener(); + + protected ServiceDirectory serviceDirectory = new DefaultServiceDirectory(); + + private Map<Integer, Set<PendingNext>> pendingForwards = Maps.newConcurrentMap(); + + private ExecutorService executorService; + + private String policy; + private Map<DeviceId, FlowObjectiveCompositionTree> deviceCompositionTreeMap; + + @Activate + protected void activate() { + executorService = newFixedThreadPool(4, groupedThreads("onos/objective-installer", "%d")); + flowObjectiveStore.setDelegate(delegate); + mastershipService.addListener(mastershipListener); + deviceService.addListener(deviceListener); + deviceService.getDevices().forEach(device -> setupPipelineHandler(device.id())); + deviceCompositionTreeMap = Maps.newConcurrentMap(); + log.info("Started"); + } + + @Deactivate + protected void deactivate() { + flowObjectiveStore.unsetDelegate(delegate); + mastershipService.removeListener(mastershipListener); + deviceService.removeListener(deviceListener); + executorService.shutdown(); + pipeliners.clear(); + driverHandlers.clear(); + deviceCompositionTreeMap.clear(); + log.info("Stopped"); + } + + /** + * Task that passes the flow objective down to the driver. The task will + * make a few attempts to find the appropriate driver, then eventually give + * up and report an error if no suitable driver could be found. + */ + private class ObjectiveInstaller implements Runnable { + private final DeviceId deviceId; + private final Objective objective; + + private final int numAttempts; + + public ObjectiveInstaller(DeviceId deviceId, Objective objective) { + this(deviceId, objective, 1); + } + + public ObjectiveInstaller(DeviceId deviceId, Objective objective, int attemps) { + this.deviceId = checkNotNull(deviceId); + this.objective = checkNotNull(objective); + this.numAttempts = checkNotNull(attemps); + } + + @Override + public void run() { + try { + Pipeliner pipeliner = getDevicePipeliner(deviceId); + + if (pipeliner != null) { + if (objective instanceof NextObjective) { + pipeliner.next((NextObjective) objective); + } else if (objective instanceof ForwardingObjective) { + pipeliner.forward((ForwardingObjective) objective); + } else { + pipeliner.filter((FilteringObjective) objective); + } + } else if (numAttempts < INSTALL_RETRY_ATTEMPTS) { + Thread.sleep(INSTALL_RETRY_INTERVAL); + executorService.submit(new ObjectiveInstaller(deviceId, objective, numAttempts + 1)); + } else { + // Otherwise we've tried a few times and failed, report an + // error back to the user. + objective.context().ifPresent( + c -> c.onError(objective, ObjectiveError.DEVICEMISSING)); + } + } catch (Exception e) { + log.warn("Exception while installing flow objective", e); + } + } + } + + @Override + public void filter(DeviceId deviceId, FilteringObjective filteringObjective) { + checkPermission(FLOWRULE_WRITE); + + List<FilteringObjective> filteringObjectives + = this.deviceCompositionTreeMap.get(deviceId).updateFilter(filteringObjective); + for (FilteringObjective tmp : filteringObjectives) { + executorService.submit(new ObjectiveInstaller(deviceId, tmp)); + } + } + + @Override + public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) { + checkPermission(FLOWRULE_WRITE); + + if (queueObjective(deviceId, forwardingObjective)) { + return; + } + List<ForwardingObjective> forwardingObjectives + = this.deviceCompositionTreeMap.get(deviceId).updateForward(forwardingObjective); + for (ForwardingObjective tmp : forwardingObjectives) { + executorService.submit(new ObjectiveInstaller(deviceId, tmp)); + } + } + + @Override + public void next(DeviceId deviceId, NextObjective nextObjective) { + checkPermission(FLOWRULE_WRITE); + + List<NextObjective> nextObjectives = this.deviceCompositionTreeMap.get(deviceId).updateNext(nextObjective); + for (NextObjective tmp : nextObjectives) { + executorService.submit(new ObjectiveInstaller(deviceId, tmp)); + } + } + + @Override + public int allocateNextId() { + checkPermission(FLOWRULE_WRITE); + + return flowObjectiveStore.allocateNextId(); + } + + private boolean queueObjective(DeviceId deviceId, ForwardingObjective fwd) { + if (fwd.nextId() != null && + flowObjectiveStore.getNextGroup(fwd.nextId()) == null) { + log.trace("Queuing forwarding objective for nextId {}", fwd.nextId()); + if (pendingForwards.putIfAbsent(fwd.nextId(), + Sets.newHashSet(new PendingNext(deviceId, fwd))) != null) { + Set<PendingNext> pending = pendingForwards.get(fwd.nextId()); + pending.add(new PendingNext(deviceId, fwd)); + } + return true; + } + return false; + } + + @Override + public void initPolicy(String policy) { + this.policy = policy; + deviceService.getDevices().forEach(device -> + this.deviceCompositionTreeMap.put(device.id(), FlowObjectiveCompositionUtil.parsePolicyString(policy))); + log.info("Initialize policy {}", policy); + } + + // Retrieves the device pipeline behaviour from the cache. + private Pipeliner getDevicePipeliner(DeviceId deviceId) { + Pipeliner pipeliner = pipeliners.get(deviceId); + return pipeliner; + } + + private void setupPipelineHandler(DeviceId deviceId) { + if (defaultDriverService == null) { + // We're not ready to go to work yet. + return; + } + + // Attempt to lookup the handler in the cache + DriverHandler handler = driverHandlers.get(deviceId); + if (handler == null) { + try { + // Otherwise create it and if it has pipeline behaviour, cache it + handler = driverService.createHandler(deviceId); + if (!handler.driver().hasBehaviour(Pipeliner.class)) { + log.warn("Pipeline behaviour not supported for device {}", + deviceId); + return; + } + } catch (ItemNotFoundException e) { + log.warn("No applicable driver for device {}", deviceId); + return; + } + + driverHandlers.put(deviceId, handler); + } + + // Always (re)initialize the pipeline behaviour + log.info("Driver {} bound to device {} ... initializing driver", + handler.driver().name(), deviceId); + Pipeliner pipeliner = handler.behaviour(Pipeliner.class); + pipeliner.init(deviceId, context); + pipeliners.putIfAbsent(deviceId, pipeliner); + } + + // Triggers driver setup when the local node becomes a device master. + private class InnerMastershipListener implements MastershipListener { + @Override + public void event(MastershipEvent event) { + switch (event.type()) { + case MASTER_CHANGED: + log.debug("mastership changed on device {}", event.subject()); + if (deviceService.isAvailable(event.subject())) { + setupPipelineHandler(event.subject()); + } + break; + case BACKUPS_CHANGED: + break; + default: + break; + } + } + } + + // Triggers driver setup when a device is (re)detected. + private class InnerDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + log.debug("Device either added or availability changed {}", + event.subject().id()); + if (deviceService.isAvailable(event.subject().id())) { + log.debug("Device is now available {}", event.subject().id()); + setupPipelineHandler(event.subject().id()); + } + break; + case DEVICE_UPDATED: + break; + case DEVICE_REMOVED: + break; + case DEVICE_SUSPENDED: + break; + case PORT_ADDED: + break; + case PORT_UPDATED: + break; + case PORT_REMOVED: + break; + default: + break; + } + } + } + + // Processing context for initializing pipeline driver behaviours. + private class InnerPipelineContext implements PipelinerContext { + @Override + public ServiceDirectory directory() { + return serviceDirectory; + } + + @Override + public FlowObjectiveStore store() { + return flowObjectiveStore; + } + } + + private class InternalStoreDelegate implements FlowObjectiveStoreDelegate { + @Override + public void notify(ObjectiveEvent event) { + log.debug("Received notification of obj event {}", event); + Set<PendingNext> pending = pendingForwards.remove(event.subject()); + + if (pending == null) { + log.debug("Nothing pending for this obj event"); + return; + } + + log.debug("Processing pending forwarding objectives {}", pending.size()); + + pending.forEach(p -> getDevicePipeliner(p.deviceId()) + .forward(p.forwardingObjective())); + + } + } + + /** + * Data class used to hold a pending forwarding objective that could not + * be processed because the associated next object was not present. + */ + private class PendingNext { + private final DeviceId deviceId; + private final ForwardingObjective fwd; + + public PendingNext(DeviceId deviceId, ForwardingObjective fwd) { + this.deviceId = deviceId; + this.fwd = fwd; + } + + public DeviceId deviceId() { + return deviceId; + } + + public ForwardingObjective forwardingObjective() { + return fwd; + } + } + + public static String forwardingObjectiveToString(ForwardingObjective forwardingObjective) { + String str = forwardingObjective.priority() + " "; + str += "selector( "; + for (Criterion criterion : forwardingObjective.selector().criteria()) { + str += criterion + " "; + } + str += ") treatment( "; + for (Instruction instruction : forwardingObjective.treatment().allInstructions()) { + str += instruction + " "; + } + str += ")"; + return str; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionTree.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionTree.java new file mode 100644 index 00000000..152622b2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionTree.java @@ -0,0 +1,271 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Provides a policy tree to store all flow tables for each device. + * + * Note: This class uses in-memory structures and is not yet distributed. + */ +public class FlowObjectiveCompositionTree { + + public FlowObjectiveCompositionManager.PolicyOperator operator; + public FlowObjectiveCompositionTree leftChild; + public FlowObjectiveCompositionTree rightChild; + public short applicationId; + protected FilterTable filterTable; + protected ForwardTable forwardTable; + protected NextTable nextTable; + + protected int priorityMultiplier; + protected int priorityAddend; + + public FlowObjectiveCompositionTree(short applicationId) { + this.operator = FlowObjectiveCompositionManager.PolicyOperator.Application; + this.leftChild = null; + this.rightChild = null; + this.applicationId = applicationId; + this.filterTable = new FilterTable(); + this.forwardTable = new ForwardTable(); + this.nextTable = new NextTable(); + this.priorityMultiplier = 10; + this.priorityAddend = 10; + } + + public FlowObjectiveCompositionTree(Character ch) { + switch (ch) { + case '+': + this.operator = FlowObjectiveCompositionManager.PolicyOperator.Parallel; + break; + case '>': + this.operator = FlowObjectiveCompositionManager.PolicyOperator.Sequential; + break; + case '/': + this.operator = FlowObjectiveCompositionManager.PolicyOperator.Override; + break; + default: + this.operator = FlowObjectiveCompositionManager.PolicyOperator.Application; + break; + } + this.leftChild = null; + this.rightChild = null; + this.applicationId = (short) -1; + this.filterTable = new FilterTable(); + this.forwardTable = new ForwardTable(); + this.nextTable = new NextTable(); + this.priorityMultiplier = 10; + this.priorityAddend = 10; + } + + protected List<FilteringObjective> updateFilter(FilteringObjective filteringObjective) { + switch (this.operator) { + case Parallel: + return updateFilterParallel(filteringObjective); + case Sequential: + return updateFilterSequential(filteringObjective); + case Override: + return updateFilterOverride(filteringObjective); + case Application: + if (filteringObjective.appId().id() == this.applicationId) { + return this.filterTable.updateFilter(filteringObjective); + } else { + return new ArrayList<>(); + } + default: + return new ArrayList<>(); + } + } + + // Parallel composition: the filter set is the union of the children + protected List<FilteringObjective> updateFilterParallel(FilteringObjective filteringObjective) { + List<FilteringObjective> leftUpdates = this.leftChild.updateFilter(filteringObjective); + List<FilteringObjective> rightUpdates = this.rightChild.updateFilter(filteringObjective); + + List<FilteringObjective> updates = new ArrayList<>(); + updates.addAll(leftUpdates); + updates.addAll(rightUpdates); + + return this.filterTable.updateFilter(updates); + } + + // Sequential composition: the filter set is the filter set of the left child + protected List<FilteringObjective> updateFilterSequential(FilteringObjective filteringObjective) { + List<FilteringObjective> leftUpdates = this.leftChild.updateFilter(filteringObjective); + List<FilteringObjective> rightUpdates = this.rightChild.updateFilter(filteringObjective); + return this.filterTable.updateFilter(leftUpdates); + } + + // Override composition: the filter set is the filter set of the left child + protected List<FilteringObjective> updateFilterOverride(FilteringObjective filteringObjective) { + List<FilteringObjective> leftUpdates = this.leftChild.updateFilter(filteringObjective); + List<FilteringObjective> rightUpdates = this.rightChild.updateFilter(filteringObjective); + return this.filterTable.updateFilter(leftUpdates); + } + + public List<ForwardingObjective> updateForward(ForwardingObjective forwardingObjective) { + return this.updateForwardNode(forwardingObjective).toForwardingObjectiveList(); + } + + public ForwardUpdateTable updateForwardNode(ForwardingObjective forwardingObjective) { + switch (this.operator) { + case Parallel: + case Sequential: + case Override: + return updateForwardComposition(forwardingObjective); + case Application: + if (forwardingObjective.appId().id() == this.applicationId) { + return this.forwardTable.updateForward(forwardingObjective); + } else { + return (new ForwardUpdateTable()); + } + default: + return (new ForwardUpdateTable()); + } + } + + protected ForwardUpdateTable updateForwardComposition(ForwardingObjective forwardingObjective) { + ForwardUpdateTable leftUpdates = this.leftChild.updateForwardNode(forwardingObjective); + ForwardUpdateTable rightUpdates = this.rightChild.updateForwardNode(forwardingObjective); + + List<ForwardingObjective> addUpdates = new ArrayList<>(); + List<ForwardingObjective> removeUpdates = new ArrayList<>(); + // Handle ADD + if (this.operator == FlowObjectiveCompositionManager.PolicyOperator.Parallel + || this.operator == FlowObjectiveCompositionManager.PolicyOperator.Sequential) { + for (ForwardingObjective fo1 : leftUpdates.addObjectives) { + for (ForwardingObjective fo2 : this.rightChild.forwardTable.getForwardingObjectives()) { + ForwardingObjective composedFo = null; + if (this.operator == FlowObjectiveCompositionManager.PolicyOperator.Parallel) { + composedFo = FlowObjectiveCompositionUtil.composeParallel(fo1, fo2); + } else { + composedFo = FlowObjectiveCompositionUtil.composeSequential(fo1, fo2, this.priorityMultiplier); + } + if (composedFo != null) { + addUpdates.add(composedFo); + this.leftChild.forwardTable.addGeneratedParentForwardingObjective(fo1, composedFo); + this.rightChild.forwardTable.addGeneratedParentForwardingObjective(fo2, composedFo); + } + } + } + Collection<ForwardingObjective> leftTableWithoutAdd = FlowObjectiveCompositionUtil + .minusForwardingObjectives(this.leftChild.forwardTable.getForwardingObjectives(), + leftUpdates.addObjectives); + for (ForwardingObjective fo1 : leftTableWithoutAdd) { + for (ForwardingObjective fo2 : rightUpdates.addObjectives) { + ForwardingObjective composedFo = null; + if (this.operator == FlowObjectiveCompositionManager.PolicyOperator.Parallel) { + composedFo = FlowObjectiveCompositionUtil.composeParallel(fo1, fo2); + } else { + composedFo = FlowObjectiveCompositionUtil.composeSequential(fo1, fo2, this.priorityMultiplier); + } + if (composedFo != null) { + addUpdates.add(composedFo); + this.leftChild.forwardTable.addGeneratedParentForwardingObjective(fo1, composedFo); + this.rightChild.forwardTable.addGeneratedParentForwardingObjective(fo2, composedFo); + } + } + } + } else { + for (ForwardingObjective fo : leftUpdates.addObjectives) { + ForwardingObjective composedFo = FlowObjectiveCompositionUtil.composeOverride(fo, this.priorityAddend); + addUpdates.add(composedFo); + this.leftChild.forwardTable.addGeneratedParentForwardingObjective(fo, composedFo); + } + for (ForwardingObjective fo : rightUpdates.addObjectives) { + ForwardingObjective composedFo = FlowObjectiveCompositionUtil.composeOverride(fo, 0); + addUpdates.add(composedFo); + this.rightChild.forwardTable.addGeneratedParentForwardingObjective(fo, composedFo); + } + } + + // Handle REMOVE + for (ForwardingObjective fo : leftUpdates.removeObjectives) { + List<ForwardingObjective> fos = this.leftChild.forwardTable + .getGeneratedParentForwardingObjectiveForRemove(fo); + removeUpdates.addAll(fos); + } + this.leftChild.forwardTable.deleteGeneratedParentForwardingObjective(leftUpdates.removeObjectives); + for (ForwardingObjective fo : rightUpdates.removeObjectives) { + List<ForwardingObjective> fos = this.rightChild.forwardTable + .getGeneratedParentForwardingObjectiveForRemove(fo); + removeUpdates.addAll(fos); + } + this.rightChild.forwardTable.deleteGeneratedParentForwardingObjective(rightUpdates.removeObjectives); + + ForwardUpdateTable updates = new ForwardUpdateTable(); + updates.addUpdateTable(this.forwardTable.updateForward(addUpdates)); + updates.addUpdateTable(this.forwardTable.updateForward(removeUpdates)); + return updates; + } + + public List<NextObjective> updateNext(NextObjective nextObjective) { + switch (this.operator) { + case Parallel: + case Sequential: + case Override: + return updateNextComposition(nextObjective); + case Application: + if (nextObjective.appId().id() == this.applicationId) { + return this.nextTable.updateNext(nextObjective); + } else { + return new ArrayList<>(); + } + default: + return new ArrayList<>(); + } + } + + // Next: the union of the children + protected List<NextObjective> updateNextComposition(NextObjective nextObjective) { + List<NextObjective> leftUpdates = this.leftChild.updateNext(nextObjective); + List<NextObjective> rightUpdates = this.rightChild.updateNext(nextObjective); + + List<NextObjective> updates = new ArrayList<>(); + updates.addAll(leftUpdates); + updates.addAll(rightUpdates); + + return this.nextTable.updateNext(updates); + } + + @Override + public String toString() { + String str = null; + switch (this.operator) { + case Parallel: + str = "(" + this.leftChild + "+" + this.rightChild + ")"; + break; + case Sequential: + str = "(" + this.leftChild + ">" + this.rightChild + ")"; + break; + case Override: + str = "(" + this.leftChild + "/" + this.rightChild + ")"; + break; + default: + str = " " + applicationId + " "; + break; + } + return str; + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionUtil.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionUtil.java new file mode 100644 index 00000000..137aca1e --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionUtil.java @@ -0,0 +1,488 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import org.onlab.packet.IpPrefix; +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.LambdaCriterion; +import org.onosproject.net.flow.criteria.OchSignalCriterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.criteria.VlanPcpCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * Provide util functions for FlowObjectiveComposition. + */ +public final class FlowObjectiveCompositionUtil { + + private FlowObjectiveCompositionUtil() {} + + // only work with VERSATILE + public static ForwardingObjective composeParallel(ForwardingObjective fo1, ForwardingObjective fo2) { + + TrafficSelector trafficSelector = intersectTrafficSelector(fo1.selector(), fo2.selector()); + if (trafficSelector == null) { + return null; + } + + TrafficTreatment trafficTreatment = unionTrafficTreatment(fo1.treatment(), fo2.treatment()); + + return DefaultForwardingObjective.builder() + .fromApp(fo1.appId()) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(fo1.priority() + fo2.priority()) + .withSelector(trafficSelector) + .withTreatment(trafficTreatment) + .add(); + } + + public static ForwardingObjective composeSequential(ForwardingObjective fo1, + ForwardingObjective fo2, + int priorityMultiplier) { + + TrafficSelector revertTrafficSelector = revertTreatmentSelector(fo1.treatment(), fo2.selector()); + if (revertTrafficSelector == null) { + return null; + } + + TrafficSelector trafficSelector = intersectTrafficSelector(fo1.selector(), revertTrafficSelector); + if (trafficSelector == null) { + return null; + } + + TrafficTreatment trafficTreatment = unionTrafficTreatment(fo1.treatment(), fo2.treatment()); + + return DefaultForwardingObjective.builder() + .fromApp(fo1.appId()) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(fo1.priority() * priorityMultiplier + fo2.priority()) + .withSelector(trafficSelector) + .withTreatment(trafficTreatment) + .add(); + } + + public static ForwardingObjective composeOverride(ForwardingObjective fo, int priorityAddend) { + return DefaultForwardingObjective.builder() + .fromApp(fo.appId()) + .makePermanent() + .withFlag(fo.flag()) + .withPriority(fo.priority() + priorityAddend) + .withSelector(fo.selector()) + .withTreatment(fo.treatment()) + .add(); + } + + public static TrafficSelector intersectTrafficSelector(TrafficSelector ts1, TrafficSelector ts2) { + + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + + Set<Criterion.Type> ts1IntersectTs2 = getTypeSet(ts1); + ts1IntersectTs2.retainAll(getTypeSet(ts2)); + for (Criterion.Type type : ts1IntersectTs2) { + Criterion criterion = intersectCriterion(ts1.getCriterion(type), ts2.getCriterion(type)); + if (criterion == null) { + return null; + } else { + selectorBuilder.add(criterion); + } + } + + Set<Criterion.Type> ts1MinusTs2 = getTypeSet(ts1); + ts1MinusTs2.removeAll(getTypeSet(ts2)); + for (Criterion.Type type : ts1MinusTs2) { + selectorBuilder.add(ts1.getCriterion(type)); + } + + Set<Criterion.Type> ts2MinusTs1 = getTypeSet(ts2); + ts2MinusTs1.removeAll(getTypeSet(ts1)); + for (Criterion.Type type : ts2MinusTs1) { + selectorBuilder.add(ts2.getCriterion(type)); + } + + return selectorBuilder.build(); + } + + public static TrafficTreatment unionTrafficTreatment(TrafficTreatment tt1, TrafficTreatment tt2) { + + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); + + for (Instruction instruction : tt1.allInstructions()) { + treatmentBuilder.add(instruction); + } + + for (Instruction instruction : tt2.allInstructions()) { + treatmentBuilder.add(instruction); + } + + return treatmentBuilder.build(); + } + + public static TrafficSelector revertTreatmentSelector(TrafficTreatment trafficTreatment, + TrafficSelector trafficSelector) { + + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + + Map<Criterion.Type, Criterion> criterionMap = new HashMap<>(); + for (Criterion criterion : trafficSelector.criteria()) { + criterionMap.put(criterion.type(), criterion); + } + + for (Instruction instruction : trafficTreatment.allInstructions()) { + switch (instruction.type()) { + case DROP: + return null; + case OUTPUT: + break; + case GROUP: + break; + case L0MODIFICATION: { + L0ModificationInstruction l0 = (L0ModificationInstruction) instruction; + switch (l0.subtype()) { + case LAMBDA: + if (criterionMap.containsKey(Criterion.Type.OCH_SIGID)) { + if (((LambdaCriterion) criterionMap.get((Criterion.Type.OCH_SIGID))).lambda() + == ((L0ModificationInstruction.ModLambdaInstruction) l0).lambda()) { + criterionMap.remove(Criterion.Type.OCH_SIGID); + } else { + return null; + } + } else { + break; + } + case OCH: + if (criterionMap.containsKey(Criterion.Type.OCH_SIGID)) { + if (((OchSignalCriterion) criterionMap.get((Criterion.Type.OCH_SIGID))).lambda() + .equals(((L0ModificationInstruction.ModOchSignalInstruction) l0).lambda())) { + criterionMap.remove(Criterion.Type.OCH_SIGID); + } else { + return null; + } + } else { + break; + } + default: + break; + } + break; + } + case L2MODIFICATION: { + L2ModificationInstruction l2 = (L2ModificationInstruction) instruction; + switch (l2.subtype()) { + case ETH_SRC: + if (criterionMap.containsKey(Criterion.Type.ETH_SRC)) { + if (((EthCriterion) criterionMap.get((Criterion.Type.ETH_SRC))).mac() + .equals(((L2ModificationInstruction.ModEtherInstruction) l2).mac())) { + criterionMap.remove(Criterion.Type.ETH_SRC); + } else { + return null; + } + } else { + break; + } + case ETH_DST: + if (criterionMap.containsKey(Criterion.Type.ETH_DST)) { + if (((EthCriterion) criterionMap.get((Criterion.Type.ETH_DST))).mac() + .equals(((L2ModificationInstruction.ModEtherInstruction) l2).mac())) { + criterionMap.remove(Criterion.Type.ETH_DST); + } else { + return null; + } + } else { + break; + } + case VLAN_ID: + if (criterionMap.containsKey(Criterion.Type.VLAN_VID)) { + if (((VlanIdCriterion) criterionMap.get((Criterion.Type.VLAN_VID))).vlanId() + .equals(((L2ModificationInstruction.ModVlanIdInstruction) l2).vlanId())) { + criterionMap.remove(Criterion.Type.VLAN_VID); + } else { + return null; + } + } else { + break; + } + case VLAN_PCP: + if (criterionMap.containsKey(Criterion.Type.VLAN_PCP)) { + if (((VlanPcpCriterion) criterionMap.get((Criterion.Type.VLAN_PCP))).priority() + == ((L2ModificationInstruction.ModVlanPcpInstruction) l2).vlanPcp()) { + criterionMap.remove(Criterion.Type.VLAN_PCP); + } else { + return null; + } + } else { + break; + } + case MPLS_LABEL: + if (criterionMap.containsKey(Criterion.Type.MPLS_LABEL)) { + if (((MplsCriterion) criterionMap.get((Criterion.Type.MPLS_LABEL))).label() + .equals(((L2ModificationInstruction.ModMplsLabelInstruction) l2).mplsLabel())) { + criterionMap.remove(Criterion.Type.ETH_DST); + } else { + return null; + } + } else { + break; + } + default: + break; + } + break; + } + case TABLE: + break; + case L3MODIFICATION: { + L3ModificationInstruction l3 = (L3ModificationInstruction) instruction; + switch (l3.subtype()) { + case IPV4_SRC: + if (criterionMap.containsKey(Criterion.Type.IPV4_SRC)) { + if (((IPCriterion) criterionMap.get(Criterion.Type.IPV4_SRC)).ip() + .contains(((L3ModificationInstruction.ModIPInstruction) l3).ip())) { + criterionMap.remove(Criterion.Type.IPV4_SRC); + } else { + return null; + } + } else { + break; + } + case IPV4_DST: + if (criterionMap.containsKey(Criterion.Type.IPV4_DST)) { + if (((IPCriterion) criterionMap.get(Criterion.Type.IPV4_DST)).ip() + .contains(((L3ModificationInstruction.ModIPInstruction) l3).ip())) { + criterionMap.remove(Criterion.Type.IPV4_DST); + } else { + return null; + } + } else { + break; + } + case IPV6_SRC: + if (criterionMap.containsKey(Criterion.Type.IPV6_SRC)) { + if (((IPCriterion) criterionMap.get(Criterion.Type.IPV6_SRC)).ip() + .contains(((L3ModificationInstruction.ModIPInstruction) l3).ip())) { + criterionMap.remove(Criterion.Type.IPV6_SRC); + } else { + return null; + } + } else { + break; + } + case IPV6_DST: + if (criterionMap.containsKey(Criterion.Type.IPV6_DST)) { + if (((IPCriterion) criterionMap.get(Criterion.Type.IPV6_DST)).ip() + .contains(((L3ModificationInstruction.ModIPInstruction) l3).ip())) { + criterionMap.remove(Criterion.Type.IPV6_DST); + } else { + return null; + } + } else { + break; + } + case IPV6_FLABEL: + if (criterionMap.containsKey(Criterion.Type.IPV6_FLABEL)) { + if (((IPv6FlowLabelCriterion) criterionMap.get(Criterion.Type.IPV6_FLABEL)).flowLabel() + == (((L3ModificationInstruction.ModIPv6FlowLabelInstruction) l3).flowLabel())) { + criterionMap.remove(Criterion.Type.IPV4_SRC); + } else { + return null; + } + } else { + break; + } + default: + break; + } + break; + } + case METADATA: + break; + default: + break; + } + } + + for (Criterion criterion : criterionMap.values()) { + selectorBuilder.add(criterion); + } + + return selectorBuilder.build(); + } + + public static Set<Criterion.Type> getTypeSet(TrafficSelector trafficSelector) { + Set<Criterion.Type> typeSet = new HashSet<>(); + for (Criterion criterion : trafficSelector.criteria()) { + typeSet.add(criterion.type()); + } + return typeSet; + } + + public static Criterion intersectCriterion(Criterion c1, Criterion c2) { + switch (c1.type()) { + case IPV4_SRC: { + IpPrefix ipPrefix = intersectIpPrefix(((IPCriterion) c1).ip(), ((IPCriterion) c2).ip()); + if (ipPrefix == null) { + return null; + } else { + return Criteria.matchIPSrc(ipPrefix); + } + } + case IPV4_DST: { + IpPrefix ipPrefix = intersectIpPrefix(((IPCriterion) c1).ip(), ((IPCriterion) c2).ip()); + if (ipPrefix == null) { + return null; + } else { + return Criteria.matchIPDst(ipPrefix); + } + } + case IPV6_SRC: { + IpPrefix ipPrefix = intersectIpPrefix(((IPCriterion) c1).ip(), ((IPCriterion) c2).ip()); + if (ipPrefix == null) { + return null; + } else { + return Criteria.matchIPv6Src(ipPrefix); + } + } + case IPV6_DST: { + IpPrefix ipPrefix = intersectIpPrefix(((IPCriterion) c1).ip(), ((IPCriterion) c2).ip()); + if (ipPrefix == null) { + return null; + } else { + return Criteria.matchIPv6Dst(ipPrefix); + } + } + default: + if (!c1.equals(c2)) { + return null; + } else { + return c1; + } + } + } + + public static IpPrefix intersectIpPrefix(IpPrefix ip1, IpPrefix ip2) { + if (ip1.contains(ip2)) { + return ip1; + } else if (ip2.contains(ip1)) { + return ip2; + } else { + return null; + } + } + + public static FlowObjectiveCompositionTree parsePolicyString(String policy) { + List<FlowObjectiveCompositionTree> postfix = transformToPostfixForm(policy); + return buildPolicyTree(postfix); + } + + private static List<FlowObjectiveCompositionTree> transformToPostfixForm(String policy) { + Stack<Character> stack = new Stack<>(); + List<FlowObjectiveCompositionTree> postfix = new ArrayList<>(); + + for (int i = 0; i < policy.length(); i++) { + Character ch = policy.charAt(i); + if (Character.isDigit(ch)) { + + int applicationId = ch - '0'; + while (i + 1 < policy.length() && Character.isDigit(policy.charAt(i + 1))) { + i++; + applicationId = applicationId * 10 + policy.charAt(i) - '0'; + } + + postfix.add(new FlowObjectiveCompositionTree((short) applicationId)); + } else if (ch == '(') { + stack.push(ch); + } else if (ch == ')') { + while (stack.peek() != '(') { + postfix.add(new FlowObjectiveCompositionTree(stack.pop())); + } + stack.pop(); + } else { + while (!stack.isEmpty() && compareOperatorPriority(stack.peek(), ch)) { + postfix.add(new FlowObjectiveCompositionTree(stack.pop())); + } + stack.push(ch); + } + } + while (!stack.isEmpty()) { + postfix.add(new FlowObjectiveCompositionTree(stack.pop())); + } + + return postfix; + } + + private static boolean compareOperatorPriority(char peek, char cur) { + if (peek == '/' && (cur == '+' || cur == '>' || cur == '/')) { + return true; + } else if (peek == '>' && (cur == '+' || cur == '>')) { + return true; + } else if (peek == '+' && cur == '+') { + return true; + } + return false; + } + + private static FlowObjectiveCompositionTree buildPolicyTree(List<FlowObjectiveCompositionTree> postfix) { + Stack<FlowObjectiveCompositionTree> stack = new Stack<>(); + for (FlowObjectiveCompositionTree node : postfix) { + if (node.operator == FlowObjectiveCompositionManager.PolicyOperator.Application) { + stack.push(node); + } else { + node.rightChild = stack.pop(); + node.leftChild = stack.pop(); + stack.push(node); + } + } + return stack.pop(); + } + + public static Collection<ForwardingObjective> minusForwardingObjectives(Collection<ForwardingObjective> fo1, + Collection<ForwardingObjective> fo2) { + Map<Integer, ForwardingObjective> map = new HashMap<>(); + for (ForwardingObjective fo : fo1) { + map.put(fo.id(), fo); + } + for (ForwardingObjective fo : fo2) { + map.remove(fo.id()); + } + return map.values(); + } + + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardTable.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardTable.java new file mode 100644 index 00000000..1384bbe2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardTable.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Provides a table to store Forward. + */ +public class ForwardTable { + + protected Map<Integer, ForwardingObjective> forwardMap; + protected Map<Integer, List<ForwardingObjective>> generatedParentForwardingObjectiveMap; + + public ForwardTable() { + this.forwardMap = new HashMap<>(); + this.generatedParentForwardingObjectiveMap = new HashMap<>(); + } + + public ForwardUpdateTable updateForward(ForwardingObjective forwardingObjective) { + ForwardUpdateTable updates = new ForwardUpdateTable(); + switch (forwardingObjective.op()) { + case ADD: + this.forwardMap.put(forwardingObjectiveHash(forwardingObjective), forwardingObjective); + this.generatedParentForwardingObjectiveMap + .put(forwardingObjectiveHash(forwardingObjective), new ArrayList<>()); + updates.addObjectives.add(forwardingObjective); + break; + case REMOVE: + if (this.forwardMap.remove(forwardingObjectiveHash(forwardingObjective)) != null) { + updates.removeObjectives.add(forwardingObjective); + } + break; + default: + break; + } + return updates; + } + + public ForwardUpdateTable updateForward(List<ForwardingObjective> forwardingObjectives) { + ForwardUpdateTable updates = new ForwardUpdateTable(); + for (ForwardingObjective forwardingObjective : forwardingObjectives) { + updates.addUpdateTable(this.updateForward(forwardingObjective)); + } + return updates; + } + + public void addGeneratedParentForwardingObjective(ForwardingObjective child, ForwardingObjective parent) { + this.generatedParentForwardingObjectiveMap.get(forwardingObjectiveHash(child)).add(parent); + } + + public void deleteGeneratedParentForwardingObjective(List<ForwardingObjective> children) { + for (ForwardingObjective fo : children) { + this.generatedParentForwardingObjectiveMap.remove(forwardingObjectiveHash(fo)); + } + } + + private List<ForwardingObjective> getGeneratedParentForwardingObjective(ForwardingObjective child) { + return this.generatedParentForwardingObjectiveMap.get(forwardingObjectiveHash(child)); + } + + public List<ForwardingObjective> getGeneratedParentForwardingObjectiveForRemove(ForwardingObjective child) { + List<ForwardingObjective> fos = this.generatedParentForwardingObjectiveMap.get(forwardingObjectiveHash(child)); + List<ForwardingObjective> removeFos = new ArrayList<>(); + for (ForwardingObjective fo : fos) { + removeFos.add(DefaultForwardingObjective.builder() + .fromApp(fo.appId()) + .makePermanent() + .withFlag(fo.flag()) + .withPriority(fo.priority()) + .withSelector(fo.selector()) + .withTreatment(fo.treatment()) + .remove()); + } + return removeFos; + } + + public Collection<ForwardingObjective> getForwardingObjectives() { + return this.forwardMap.values(); + } + + public static int forwardingObjectiveHash(ForwardingObjective forwardingObjective) { + return Objects.hash(forwardingObjective.selector(), forwardingObjective.flag(), + forwardingObjective.permanent(), forwardingObjective.timeout(), + forwardingObjective.appId(), forwardingObjective.priority(), + forwardingObjective.nextId(), forwardingObjective.treatment()); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardUpdateTable.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardUpdateTable.java new file mode 100644 index 00000000..9818cfd5 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardUpdateTable.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import org.onosproject.net.flowobjective.ForwardingObjective; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides an update table for Forward. + */ +public class ForwardUpdateTable { + public List<ForwardingObjective> addObjectives; + public List<ForwardingObjective> removeObjectives; + + public ForwardUpdateTable() { + this.addObjectives = new ArrayList<>(); + this.removeObjectives = new ArrayList<>(); + } + + public void addUpdateTable(ForwardUpdateTable updateTable) { + this.addObjectives.addAll(updateTable.addObjectives); + this.removeObjectives.addAll(updateTable.removeObjectives); + } + + public List<ForwardingObjective> toForwardingObjectiveList() { + List<ForwardingObjective> forwardingObjectives = new ArrayList<>(); + forwardingObjectives.addAll(this.addObjectives); + forwardingObjectives.addAll(this.removeObjectives); + return forwardingObjectives; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/NextTable.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/NextTable.java new file mode 100644 index 00000000..e2787edd --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/NextTable.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 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.net.flowobjective.impl.composition; + +import org.onosproject.net.flowobjective.NextObjective; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides a table to store Next. + */ +public class NextTable { + + protected Map<Integer, NextObjective> nextMap; + + public NextTable() { + this.nextMap = new HashMap<>(); + } + + public List<NextObjective> updateNext(NextObjective nextObjective) { + List<NextObjective> updates = new ArrayList<>(); + switch (nextObjective.op()) { + case ADD: + this.nextMap.put(nextObjective.id(), nextObjective); + updates.add(nextObjective); + break; + case REMOVE: + this.nextMap.remove(nextObjective.id()); + updates.add(nextObjective); + break; + default: + break; + } + return updates; + } + + public List<NextObjective> updateNext(List<NextObjective> nextObjectives) { + List<NextObjective> updates = new ArrayList<>(); + for (NextObjective nextObjective : nextObjectives) { + updates.addAll(this.updateNext(nextObjective)); + } + return updates; + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/package-info.java new file mode 100644 index 00000000..da2a9850 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Prototype of a composition mechanism for flow objective composition. + */ +package org.onosproject.net.flowobjective.impl.composition;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/package-info.java new file mode 100644 index 00000000..c0779dc2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementations of the flow objective programming subsystem. + */ +package org.onosproject.net.flowobjective.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java new file mode 100644 index 00000000..96e9b198 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java @@ -0,0 +1,318 @@ +/* + * Copyright 2015 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.net.group.impl; + +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.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupOperations; +import org.onosproject.net.group.GroupProvider; +import org.onosproject.net.group.GroupProviderRegistry; +import org.onosproject.net.group.GroupProviderService; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.group.GroupStore; +import org.onosproject.net.group.GroupStore.UpdateType; +import org.onosproject.net.group.GroupStoreDelegate; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Collections; + +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + + +/** + * Provides implementation of the group service APIs. + */ +@Component(immediate = true) +@Service +public class GroupManager + extends AbstractListenerProviderRegistry<GroupEvent, GroupListener, + GroupProvider, GroupProviderService> + implements GroupService, GroupProviderRegistry { + + private final Logger log = getLogger(getClass()); + + private final GroupStoreDelegate delegate = new InternalGroupStoreDelegate(); + private final DeviceListener deviceListener = new InternalDeviceListener(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected GroupStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Activate + public void activate() { + store.setDelegate(delegate); + eventDispatcher.addSink(GroupEvent.class, listenerRegistry); + deviceService.addListener(deviceListener); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + eventDispatcher.removeSink(GroupEvent.class); + log.info("Stopped"); + } + + /** + * Create a group in the specified device with the provided parameters. + * + * @param groupDesc group creation parameters + */ + @Override + public void addGroup(GroupDescription groupDesc) { + checkPermission(GROUP_WRITE); + store.storeGroupDescription(groupDesc); + } + + /** + * Return a group object associated to an application cookie. + * <p> + * NOTE1: The presence of group object in the system does not + * guarantee that the "group" is actually created in device. + * GROUP_ADDED notification would confirm the creation of + * this group in data plane. + * + * @param deviceId device identifier + * @param appCookie application cookie to be used for lookup + * @return group associated with the application cookie or + * NULL if Group is not found for the provided cookie + */ + @Override + public Group getGroup(DeviceId deviceId, GroupKey appCookie) { + checkPermission(GROUP_READ); + return store.getGroup(deviceId, appCookie); + } + + /** + * Append buckets to existing group. The caller can optionally + * associate a new cookie during this updation. GROUP_UPDATED or + * GROUP_UPDATE_FAILED notifications would be provided along with + * cookie depending on the result of the operation on the device. + * + * @param deviceId device identifier + * @param oldCookie cookie to be used to retrieve the existing group + * @param buckets immutable list of group bucket to be added + * @param newCookie immutable cookie to be used post update operation + * @param appId Application Id + */ + @Override + public void addBucketsToGroup(DeviceId deviceId, + GroupKey oldCookie, + GroupBuckets buckets, + GroupKey newCookie, + ApplicationId appId) { + checkPermission(GROUP_WRITE); + store.updateGroupDescription(deviceId, + oldCookie, + UpdateType.ADD, + buckets, + newCookie); + } + + /** + * Remove buckets from existing group. The caller can optionally + * associate a new cookie during this updation. GROUP_UPDATED or + * GROUP_UPDATE_FAILED notifications would be provided along with + * cookie depending on the result of the operation on the device. + * + * @param deviceId device identifier + * @param oldCookie cookie to be used to retrieve the existing group + * @param buckets immutable list of group bucket to be removed + * @param newCookie immutable cookie to be used post update operation + * @param appId Application Id + */ + @Override + public void removeBucketsFromGroup(DeviceId deviceId, + GroupKey oldCookie, + GroupBuckets buckets, + GroupKey newCookie, + ApplicationId appId) { + checkPermission(GROUP_WRITE); + store.updateGroupDescription(deviceId, + oldCookie, + UpdateType.REMOVE, + buckets, + newCookie); + } + + /** + * Delete a group associated to an application cookie. + * GROUP_DELETED or GROUP_DELETE_FAILED notifications would be + * provided along with cookie depending on the result of the + * operation on the device. + * + * @param deviceId device identifier + * @param appCookie application cookie to be used for lookup + * @param appId Application Id + */ + @Override + public void removeGroup(DeviceId deviceId, + GroupKey appCookie, + ApplicationId appId) { + checkPermission(GROUP_WRITE); + store.deleteGroupDescription(deviceId, appCookie); + } + + /** + * Retrieve all groups created by an application in the specified device + * as seen by current controller instance. + * + * @param deviceId device identifier + * @param appId application id + * @return collection of immutable group objects created by the application + */ + @Override + public Iterable<Group> getGroups(DeviceId deviceId, + ApplicationId appId) { + checkPermission(GROUP_READ); + return store.getGroups(deviceId); + } + + @Override + public Iterable<Group> getGroups(DeviceId deviceId) { + checkPermission(GROUP_READ); + return store.getGroups(deviceId); + } + + @Override + protected GroupProviderService createProviderService(GroupProvider provider) { + return new InternalGroupProviderService(provider); + } + + private class InternalGroupStoreDelegate implements GroupStoreDelegate { + @Override + public void notify(GroupEvent event) { + final Group group = event.subject(); + GroupProvider groupProvider = + getProvider(group.deviceId()); + GroupOperations groupOps = null; + switch (event.type()) { + case GROUP_ADD_REQUESTED: + log.debug("GROUP_ADD_REQUESTED for Group {} on device {}", + group.id(), group.deviceId()); + GroupOperation groupAddOp = GroupOperation. + createAddGroupOperation(group.id(), + group.type(), + group.buckets()); + groupOps = new GroupOperations( + Collections.singletonList(groupAddOp)); + groupProvider.performGroupOperation(group.deviceId(), groupOps); + break; + + case GROUP_UPDATE_REQUESTED: + log.debug("GROUP_UPDATE_REQUESTED for Group {} on device {}", + group.id(), group.deviceId()); + GroupOperation groupModifyOp = GroupOperation. + createModifyGroupOperation(group.id(), + group.type(), + group.buckets()); + groupOps = new GroupOperations( + Collections.singletonList(groupModifyOp)); + groupProvider.performGroupOperation(group.deviceId(), groupOps); + break; + + case GROUP_REMOVE_REQUESTED: + log.debug("GROUP_REMOVE_REQUESTED for Group {} on device {}", + group.id(), group.deviceId()); + GroupOperation groupDeleteOp = GroupOperation. + createDeleteGroupOperation(group.id(), + group.type()); + groupOps = new GroupOperations( + Collections.singletonList(groupDeleteOp)); + groupProvider.performGroupOperation(group.deviceId(), groupOps); + break; + + case GROUP_ADDED: + case GROUP_UPDATED: + case GROUP_REMOVED: + case GROUP_ADD_FAILED: + case GROUP_UPDATE_FAILED: + case GROUP_REMOVE_FAILED: + post(event); + break; + + default: + break; + } + } + } + + private class InternalGroupProviderService + extends AbstractProviderService<GroupProvider> + implements GroupProviderService { + + protected InternalGroupProviderService(GroupProvider provider) { + super(provider); + } + + @Override + public void groupOperationFailed(DeviceId deviceId, GroupOperation operation) { + store.groupOperationFailed(deviceId, operation); + } + + @Override + public void pushGroupMetrics(DeviceId deviceId, + Collection<Group> groupEntries) { + log.trace("Received group metrics from device {}", deviceId); + checkValidity(); + store.pushGroupMetrics(deviceId, groupEntries); + } + } + + private class InternalDeviceListener implements DeviceListener { + + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case DEVICE_REMOVED: + case DEVICE_AVAILABILITY_CHANGED: + if (!deviceService.isAvailable(event.subject().id())) { + log.debug("Device {} became un available; clearing initial audit status", + event.type(), event.subject().id()); + store.deviceInitialAuditCompleted(event.subject().id(), false); + } + break; + + default: + break; + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java new file mode 100644 index 00000000..641ab441 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Core subsystem for group state. + */ +package org.onosproject.net.group.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java new file mode 100644 index 00000000..68aa27f0 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014-2015 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.net.host.impl; + +import static org.slf4j.LoggerFactory.getLogger; + +import org.slf4j.Logger; +import org.onosproject.net.config.ConfigOperator; +import org.onosproject.net.config.basics.BasicHostConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.host.DefaultHostDescription; +import org.onosproject.net.host.HostDescription; + +/** + * Implementations of merge policies for various sources of host configuration + * information. This includes applications, provides, and network configurations. + */ +public final class BasicHostOperator implements ConfigOperator { + + protected static final double DEFAULT_COORD = -1.0; + private static final Logger log = getLogger(BasicHostOperator.class); + + private BasicHostOperator() { + } + + /** + * Generates a HostDescription containing fields from a HostDescription and + * a HostConfig. + * + * @param cfg the host config entity from network config + * @param descr a HostDescription + * @return HostDescription based on both sources + */ + public static HostDescription combine(BasicHostConfig cfg, HostDescription descr) { + if (cfg == null) { + return descr; + } + SparseAnnotations sa = combine(cfg, descr.annotations()); + return new DefaultHostDescription(descr.hwAddress(), descr.vlan(), descr.location(), + descr.ipAddress(), sa); + } + + /** + * Generates an annotation from an existing annotation and HostConfig. + * + * @param cfg the device config entity from network config + * @param an the annotation + * @return annotation combining both sources + */ + public static SparseAnnotations combine(BasicHostConfig cfg, SparseAnnotations an) { + DefaultAnnotations.Builder newBuilder = DefaultAnnotations.builder(); + if (cfg.name() != null) { + newBuilder.set(AnnotationKeys.NAME, cfg.name()); + } + if (cfg.latitude() != DEFAULT_COORD) { + newBuilder.set(AnnotationKeys.LATITUDE, Double.toString(cfg.latitude())); + } + if (cfg.longitude() != DEFAULT_COORD) { + newBuilder.set(AnnotationKeys.LONGITUDE, Double.toString(cfg.longitude())); + } + if (cfg.rackAddress() != null) { + newBuilder.set(AnnotationKeys.RACK_ADDRESS, cfg.rackAddress()); + } + if (cfg.owner() != null) { + newBuilder.set(AnnotationKeys.OWNER, cfg.owner()); + } + return DefaultAnnotations.union(an, newBuilder.build()); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java new file mode 100644 index 00000000..99263381 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java @@ -0,0 +1,300 @@ +/* + * Copyright 2014 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.net.host.impl; + +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.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.basics.BasicHostConfig; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.host.HostAdminService; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostEvent; +import org.onosproject.net.host.HostListener; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.HostProviderRegistry; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.host.HostService; +import org.onosproject.net.host.HostStore; +import org.onosproject.net.host.HostStoreDelegate; +import org.onosproject.net.host.PortAddresses; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + +/** + * Provides basic implementation of the host SB & NB APIs. + */ +@Component(immediate = true) +@Service +public class HostManager + extends AbstractListenerProviderRegistry<HostEvent, HostListener, HostProvider, HostProviderService> + implements HostService, HostAdminService, HostProviderRegistry { + + private final Logger log = getLogger(getClass()); + + public static final String HOST_ID_NULL = "Host ID cannot be null"; + + private final NetworkConfigListener networkConfigListener = new InternalNetworkConfigListener(); + + private HostStoreDelegate delegate = new InternalStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService networkConfigService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected InterfaceService interfaceService; + + private HostMonitor monitor; + + @Activate + public void activate() { + store.setDelegate(delegate); + eventDispatcher.addSink(HostEvent.class, listenerRegistry); + networkConfigService.addListener(networkConfigListener); + monitor = new HostMonitor(packetService, this, interfaceService); + monitor.start(); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + eventDispatcher.removeSink(HostEvent.class); + networkConfigService.removeListener(networkConfigListener); + log.info("Stopped"); + } + + @Override + protected HostProviderService createProviderService(HostProvider provider) { + monitor.registerHostProvider(provider); + return new InternalHostProviderService(provider); + } + + @Override + public int getHostCount() { + checkPermission(HOST_READ); + return store.getHostCount(); + } + + @Override + public Iterable<Host> getHosts() { + checkPermission(HOST_READ); + return store.getHosts(); + } + + @Override + public Host getHost(HostId hostId) { + checkPermission(HOST_READ); + checkNotNull(hostId, HOST_ID_NULL); + return store.getHost(hostId); + } + + @Override + public Set<Host> getHostsByVlan(VlanId vlanId) { + checkPermission(HOST_READ); + return store.getHosts(vlanId); + } + + @Override + public Set<Host> getHostsByMac(MacAddress mac) { + checkPermission(HOST_READ); + checkNotNull(mac, "MAC address cannot be null"); + return store.getHosts(mac); + } + + @Override + public Set<Host> getHostsByIp(IpAddress ip) { + checkPermission(HOST_READ); + checkNotNull(ip, "IP address cannot be null"); + return store.getHosts(ip); + } + + @Override + public Set<Host> getConnectedHosts(ConnectPoint connectPoint) { + checkPermission(HOST_READ); + checkNotNull(connectPoint, "Connection point cannot be null"); + return store.getConnectedHosts(connectPoint); + } + + @Override + public Set<Host> getConnectedHosts(DeviceId deviceId) { + checkPermission(HOST_READ); + checkNotNull(deviceId, "Device ID cannot be null"); + return store.getConnectedHosts(deviceId); + } + + @Override + public void startMonitoringIp(IpAddress ip) { + checkPermission(HOST_EVENT); + monitor.addMonitoringFor(ip); + } + + @Override + public void stopMonitoringIp(IpAddress ip) { + checkPermission(HOST_EVENT); + monitor.stopMonitoring(ip); + } + + @Override + public void requestMac(IpAddress ip) { + // FIXME!!!! Auto-generated method stub + } + + @Override + public void removeHost(HostId hostId) { + checkNotNull(hostId, HOST_ID_NULL); + HostEvent event = store.removeHost(hostId); + if (event != null) { + post(event); + } + } + + @Override + public void bindAddressesToPort(PortAddresses addresses) { + store.updateAddressBindings(addresses); + } + + @Override + public void unbindAddressesFromPort(PortAddresses portAddresses) { + store.removeAddressBindings(portAddresses); + } + + @Override + public void clearAddresses(ConnectPoint connectPoint) { + store.clearAddressBindings(connectPoint); + } + + @Override + public Set<PortAddresses> getAddressBindings() { + checkPermission(HOST_READ); + return store.getAddressBindings(); + } + + @Override + public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) { + checkPermission(HOST_READ); + return store.getAddressBindingsForPort(connectPoint); + } + + // Personalized host provider service issued to the supplied provider. + private class InternalHostProviderService + extends AbstractProviderService<HostProvider> + implements HostProviderService { + InternalHostProviderService(HostProvider provider) { + super(provider); + } + + @Override + public void hostDetected(HostId hostId, HostDescription hostDescription) { + checkNotNull(hostId, HOST_ID_NULL); + checkValidity(); + hostDescription = validateHost(hostDescription, hostId); + HostEvent event = store.createOrUpdateHost(provider().id(), hostId, + hostDescription); + if (event != null) { + post(event); + } + } + + // returns a HostDescription made from the union of the BasicHostConfig + // annotations if it exists + private HostDescription validateHost(HostDescription hostDescription, HostId hostId) { + BasicHostConfig cfg = networkConfigService.getConfig(hostId, BasicHostConfig.class); + checkState(cfg == null || cfg.isAllowed(), "Host {} is not allowed", hostId); + + return BasicHostOperator.combine(cfg, hostDescription); + } + + @Override + public void hostVanished(HostId hostId) { + checkNotNull(hostId, HOST_ID_NULL); + checkValidity(); + HostEvent event = store.removeHost(hostId); + if (event != null) { + post(event); + } + } + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements HostStoreDelegate { + @Override + public void notify(HostEvent event) { + post(event); + } + } + + // listens for NetworkConfigEvents of type BasicHostConfig and removes + // links that the config does not allow + private class InternalNetworkConfigListener implements NetworkConfigListener { + @Override + public void event(NetworkConfigEvent event) { + if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED || + event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) && + event.configClass().equals(BasicHostConfig.class)) { + log.info("Detected Host network config event {}", event.type()); + kickOutBadHost(((HostId) event.subject())); + } + } + } + + // checks if the specified host is allowed by the BasicHostConfig + // and if not, removes it + private void kickOutBadHost(HostId hostId) { + BasicHostConfig cfg = networkConfigService.getConfig(hostId, BasicHostConfig.class); + if (cfg != null && !cfg.isAllowed()) { + Host badHost = getHost(hostId); + if (badHost != null) { + removeHost(hostId); + } else { + log.info("Failed removal: Host {} does not exist", hostId); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java new file mode 100644 index 00000000..fe252368 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java @@ -0,0 +1,288 @@ +/* + * Copyright 2014-2015 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.net.host.impl; + +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.packet.ARP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP6; +import org.onlab.packet.IPv6; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.packet.ndp.NeighborDiscoveryOptions; +import org.onlab.packet.ndp.NeighborSolicitation; +import org.onlab.util.Timer; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Host; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.InterfaceIpAddress; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.provider.ProviderId; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +/** + * Monitors hosts on the dataplane to detect changes in host data. + * <p> + * The HostMonitor can monitor hosts that have already been detected for + * changes. At an application's request, it can also monitor and actively + * probe for hosts that have not yet been detected (specified by IP address). + * </p> + */ +public class HostMonitor implements TimerTask { + private PacketService packetService; + private HostManager hostManager; + private InterfaceService interfaceService; + + private final Set<IpAddress> monitoredAddresses; + + private final ConcurrentMap<ProviderId, HostProvider> hostProviders; + + private static final long DEFAULT_PROBE_RATE = 30000; // milliseconds + private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes(); + private long probeRate = DEFAULT_PROBE_RATE; + + private Timeout timeout; + + /** + * Creates a new host monitor. + * + * @param packetService packet service used to send packets on the data plane + * @param hostManager host manager used to look up host information and + * probe existing hosts + * @param interfaceService interface service for interface information + */ + public HostMonitor(PacketService packetService, HostManager hostManager, + InterfaceService interfaceService) { + + this.packetService = packetService; + this.hostManager = hostManager; + this.interfaceService = interfaceService; + + monitoredAddresses = Collections.newSetFromMap(new ConcurrentHashMap<>()); + hostProviders = new ConcurrentHashMap<>(); + } + + /** + * Adds an IP address to be monitored by the host monitor. The monitor will + * periodically probe the host to detect changes. + * + * @param ip IP address of the host to monitor + */ + void addMonitoringFor(IpAddress ip) { + monitoredAddresses.add(ip); + } + + /** + * Stops monitoring the given IP address. + * + * @param ip IP address to stop monitoring on + */ + void stopMonitoring(IpAddress ip) { + monitoredAddresses.remove(ip); + } + + /** + * Starts the host monitor. Does nothing if the monitor is already running. + */ + void start() { + synchronized (this) { + if (timeout == null) { + timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS); + } + } + } + + /** + * Stops the host monitor. + */ + void shutdown() { + synchronized (this) { + timeout.cancel(); + timeout = null; + } + } + + /** + * Registers a host provider with the host monitor. The monitor can use the + * provider to probe hosts. + * + * @param provider the host provider to register + */ + void registerHostProvider(HostProvider provider) { + hostProviders.put(provider.id(), provider); + } + + @Override + public void run(Timeout timeout) throws Exception { + for (IpAddress ip : monitoredAddresses) { + Set<Host> hosts = hostManager.getHostsByIp(ip); + + if (hosts.isEmpty()) { + sendArpNdpRequest(ip); + } else { + for (Host host : hosts) { + HostProvider provider = hostProviders.get(host.providerId()); + if (provider == null) { + hostProviders.remove(host.providerId(), null); + } else { + provider.triggerProbe(host); + } + } + } + } + + this.timeout = Timer.getTimer().newTimeout(this, probeRate, TimeUnit.MILLISECONDS); + } + + /** + * Sends an ARP or Neighbor Discovery Protocol request for the given IP + * address. + * + * @param targetIp IP address to send the request for + */ + private void sendArpNdpRequest(IpAddress targetIp) { + Interface intf = interfaceService.getMatchingInterface(targetIp); + + if (intf == null) { + return; + } + + for (InterfaceIpAddress ia : intf.ipAddresses()) { + if (ia.subnetAddress().contains(targetIp)) { + sendArpNdpProbe(intf.connectPoint(), targetIp, ia.ipAddress(), + intf.mac(), intf.vlan()); + } + } + } + + private void sendArpNdpProbe(ConnectPoint connectPoint, + IpAddress targetIp, + IpAddress sourceIp, MacAddress sourceMac, + VlanId vlan) { + Ethernet probePacket = null; + + if (targetIp.isIp4()) { + // IPv4: Use ARP + probePacket = buildArpRequest(targetIp, sourceIp, sourceMac, + vlan); + } else { + // IPv6: Use Neighbor Discovery + probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac, + vlan); + } + + List<Instruction> instructions = new ArrayList<>(); + instructions.add(Instructions.createOutput(connectPoint.port())); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(connectPoint.port()) + .build(); + + OutboundPacket outboundPacket = + new DefaultOutboundPacket(connectPoint.deviceId(), treatment, + ByteBuffer.wrap(probePacket.serialize())); + + packetService.emit(outboundPacket); + } + + private Ethernet buildArpRequest(IpAddress targetIp, IpAddress sourceIp, + MacAddress sourceMac, VlanId vlan) { + + ARP arp = new ARP(); + arp.setHardwareType(ARP.HW_TYPE_ETHERNET) + .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setProtocolAddressLength((byte) IpAddress.INET_BYTE_LENGTH) + .setOpCode(ARP.OP_REQUEST); + + arp.setSenderHardwareAddress(sourceMac.toBytes()) + .setSenderProtocolAddress(sourceIp.toOctets()) + .setTargetHardwareAddress(ZERO_MAC_ADDRESS) + .setTargetProtocolAddress(targetIp.toOctets()); + + Ethernet ethernet = new Ethernet(); + ethernet.setEtherType(Ethernet.TYPE_ARP) + .setDestinationMACAddress(MacAddress.BROADCAST) + .setSourceMACAddress(sourceMac) + .setPayload(arp); + + if (!vlan.equals(VlanId.NONE)) { + ethernet.setVlanID(vlan.toShort()); + } + + ethernet.setPad(true); + + return ethernet; + } + + private Ethernet buildNdpRequest(IpAddress targetIp, IpAddress sourceIp, + MacAddress sourceMac, VlanId vlan) { + + // Create the Ethernet packet + Ethernet ethernet = new Ethernet(); + ethernet.setEtherType(Ethernet.TYPE_IPV6) + .setDestinationMACAddress(MacAddress.BROADCAST) + .setSourceMACAddress(sourceMac); + if (!vlan.equals(VlanId.NONE)) { + ethernet.setVlanID(vlan.toShort()); + } + + // + // Create the IPv6 packet + // + // TODO: The destination IP address should be the + // solicited-node multicast address + IPv6 ipv6 = new IPv6(); + ipv6.setSourceAddress(sourceIp.toOctets()); + ipv6.setDestinationAddress(targetIp.toOctets()); + ipv6.setHopLimit((byte) 255); + + // Create the ICMPv6 packet + ICMP6 icmp6 = new ICMP6(); + icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION); + icmp6.setIcmpCode((byte) 0); + + // Create the Neighbor Solication packet + NeighborSolicitation ns = new NeighborSolicitation(); + ns.setTargetAddress(targetIp.toOctets()); + ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS, + sourceMac.toBytes()); + + icmp6.setPayload(ns); + ipv6.setPayload(icmp6); + ethernet.setPayload(ipv6); + + return ethernet; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/package-info.java new file mode 100644 index 00000000..1f7d5889 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for tracking global inventory of end-station hosts. + */ +package org.onosproject.net.host.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/CompilerRegistry.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/CompilerRegistry.java new file mode 100644 index 00000000..1b70bc67 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/CompilerRegistry.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015 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.net.intent.impl; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +// TODO: consider a better name +class CompilerRegistry { + + private final ConcurrentMap<Class<? extends Intent>, + IntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>(); + + /** + * Registers the specified compiler for the given intent class. + * + * @param cls intent class + * @param compiler intent compiler + * @param <T> the type of intent + */ + public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) { + compilers.put(cls, compiler); + } + + /** + * Unregisters the compiler for the specified intent class. + * + * @param cls intent class + * @param <T> the type of intent + */ + public <T extends Intent> void unregisterCompiler(Class<T> cls) { + compilers.remove(cls); + } + + /** + * Returns immutable set of bindings of currently registered intent compilers. + * + * @return the set of compiler bindings + */ + public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() { + return ImmutableMap.copyOf(compilers); + } + + /** + * Compiles an intent recursively. + * + * @param intent intent + * @param previousInstallables previous intent installables + * @return result of compilation + */ + List<Intent> compile(Intent intent, List<Intent> previousInstallables) { + if (intent.isInstallable()) { + return ImmutableList.of(intent); + } + + registerSubclassCompilerIfNeeded(intent); + // FIXME: get previous resources + List<Intent> installable = new ArrayList<>(); + for (Intent compiled : getCompiler(intent).compile(intent, previousInstallables, null)) { + installable.addAll(compile(compiled, previousInstallables)); + } + return installable; + } + + /** + * Returns the corresponding intent compiler to the specified intent. + * + * @param intent intent + * @param <T> the type of intent + * @return intent compiler corresponding to the specified intent + */ + private <T extends Intent> IntentCompiler<T> getCompiler(T intent) { + @SuppressWarnings("unchecked") + IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass()); + if (compiler == null) { + throw new IntentException("no compiler for class " + intent.getClass()); + } + return compiler; + } + + /** + * Registers an intent compiler of the specified intent if an intent compiler + * for the intent is not registered. This method traverses the class hierarchy of + * the intent. Once an intent compiler for a parent type is found, this method + * registers the found intent compiler. + * + * @param intent intent + */ + private void registerSubclassCompilerIfNeeded(Intent intent) { + if (!compilers.containsKey(intent.getClass())) { + Class<?> cls = intent.getClass(); + while (cls != Object.class) { + // As long as we're within the Intent class descendants + if (Intent.class.isAssignableFrom(cls)) { + IntentCompiler<?> compiler = compilers.get(cls); + if (compiler != null) { + compilers.put(intent.getClass(), compiler); + return; + } + } + cls = cls.getSuperclass(); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentAccumulator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentAccumulator.java new file mode 100644 index 00000000..54276ad4 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentAccumulator.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015 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.net.intent.impl; + +import com.google.common.collect.Maps; +import org.onlab.util.AbstractAccumulator; +import org.onosproject.net.intent.IntentBatchDelegate; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.Key; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Timer; + +/** + * An accumulator for building batches of intent operations. Only one batch should + * be in process per instance at a time. + */ +public class IntentAccumulator extends AbstractAccumulator<IntentData> { + + private static final int DEFAULT_MAX_EVENTS = 1000; + private static final int DEFAULT_MAX_IDLE_MS = 10; + private static final int DEFAULT_MAX_BATCH_MS = 50; + + // FIXME: Replace with a system-wide timer instance; + // TODO: Convert to use HashedWheelTimer or produce a variant of that; then decide which we want to adopt + private static final Timer TIMER = new Timer("onos-intent-op-batching"); + + private final IntentBatchDelegate delegate; + + private volatile boolean ready; + + /** + * Creates an intent operation accumulator. + * + * @param delegate the intent batch delegate + */ + protected IntentAccumulator(IntentBatchDelegate delegate) { + super(TIMER, DEFAULT_MAX_EVENTS, DEFAULT_MAX_BATCH_MS, DEFAULT_MAX_IDLE_MS); + this.delegate = delegate; + // Assume that the delegate is ready for work at the start + ready = true; //TODO validate the assumption that delegate is ready + } + + @Override + public void processItems(List<IntentData> items) { + ready = false; + delegate.execute(reduce(items)); + } + + private Collection<IntentData> reduce(List<IntentData> ops) { + Map<Key, IntentData> map = Maps.newHashMap(); + for (IntentData op : ops) { + map.put(op.key(), op); + } + //TODO check the version... or maybe store will handle this. + return map.values(); + } + + @Override + public boolean isReady() { + return ready; + } + + public void ready() { + ready = true; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java new file mode 100644 index 00000000..d7fa3223 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java @@ -0,0 +1,254 @@ +/* + * Copyright 2015 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.net.intent.impl; + +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.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentListener; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentStore; +import org.onosproject.net.intent.Key; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.util.Dictionary; +import java.util.Properties; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.onlab.util.Tools.get; +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * This component cleans up intents that have encountered errors or otherwise + * stalled during installation or withdrawal. + * <p> + * It periodically polls (based on configured period) for pending and CORRUPT + * intents from the store and retries. It also listens for CORRUPT event + * notifications, which signify errors in processing, and retries. + * </p> + */ +@Component(immediate = true) +public class IntentCleanup implements Runnable, IntentListener { + + private static final Logger log = getLogger(IntentCleanup.class); + + private static final int DEFAULT_PERIOD = 5; //seconds + private static final int DEFAULT_THRESHOLD = 5; //tries + + @Property(name = "enabled", boolValue = true, + label = "Enables/disables the intent cleanup component") + private boolean enabled = true; + + @Property(name = "period", intValue = DEFAULT_PERIOD, + label = "Frequency in ms between cleanup runs") + protected int period = DEFAULT_PERIOD; + private long periodMs; + + @Property(name = "retryThreshold", intValue = DEFAULT_THRESHOLD, + label = "Number of times to retry CORRUPT intent without delay") + protected int retryThreshold = DEFAULT_THRESHOLD; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentService service; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private ExecutorService executor; + private Timer timer; + private TimerTask timerTask; + + @Activate + public void activate() { + cfgService.registerProperties(getClass()); + executor = newSingleThreadExecutor(groupedThreads("onos/intent", "cleanup")); + timer = new Timer("onos-intent-cleanup-timer"); + service.addListener(this); + adjustRate(); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + service.removeListener(this); + timer.cancel(); + timerTask = null; + executor.shutdown(); + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties(); + + int newPeriod; + boolean newEnabled; + try { + String s = get(properties, "period"); + newPeriod = isNullOrEmpty(s) ? period : Integer.parseInt(s.trim()); + + s = get(properties, "retryThreshold"); + retryThreshold = isNullOrEmpty(s) ? retryThreshold : Integer.parseInt(s.trim()); + + s = get(properties, "enabled"); + newEnabled = isNullOrEmpty(s) ? enabled : Boolean.parseBoolean(s.trim()); + } catch (NumberFormatException e) { + log.warn(e.getMessage()); + newPeriod = period; + newEnabled = enabled; + } + + // Any change in the following parameters implies hard restart + if (newPeriod != period || enabled != newEnabled) { + period = newPeriod; + enabled = newEnabled; + adjustRate(); + } + + log.info("Settings: enabled={}, period={}, retryThreshold={}", + enabled, period, retryThreshold); + } + + protected void adjustRate() { + if (timerTask != null) { + timerTask.cancel(); + timerTask = null; + } + + if (enabled) { + timerTask = new TimerTask() { + @Override + public void run() { + executor.submit(IntentCleanup.this); + } + }; + + periodMs = period * 1_000; //convert to ms + timer.scheduleAtFixedRate(timerTask, periodMs, periodMs); + } + } + + + @Override + public void run() { + try { + cleanup(); + } catch (Exception e) { + log.warn("Caught exception during Intent cleanup", e); + } + } + + private void resubmitCorrupt(IntentData intentData, boolean checkThreshold) { + if (checkThreshold && intentData.errorCount() >= retryThreshold) { + return; // threshold met or exceeded + } + + switch (intentData.request()) { + case INSTALL_REQ: + service.submit(intentData.intent()); + break; + case WITHDRAW_REQ: + service.withdraw(intentData.intent()); + break; + default: + log.warn("Trying to resubmit corrupt/failed intent {} in state {} with request {}", + intentData.key(), intentData.state(), intentData.request()); + break; + } + } + + private void resubmitPendingRequest(IntentData intentData) { + switch (intentData.request()) { + case INSTALL_REQ: + service.submit(intentData.intent()); + break; + case WITHDRAW_REQ: + service.withdraw(intentData.intent()); + break; + default: + log.warn("Trying to resubmit pending intent {} in state {} with request {}", + intentData.key(), intentData.state(), intentData.request()); + break; + } + } + + /** + * Iterates through corrupt, failed and pending intents and + * re-submit/withdraw appropriately. + */ + private void cleanup() { + int corruptCount = 0, failedCount = 0, stuckCount = 0, pendingCount = 0; + + for (IntentData intentData : store.getIntentData(true, periodMs)) { + switch (intentData.state()) { + case FAILED: + resubmitCorrupt(intentData, false); + failedCount++; + break; + case CORRUPT: + resubmitCorrupt(intentData, false); + corruptCount++; + break; + case INSTALLING: //FALLTHROUGH + case WITHDRAWING: + resubmitPendingRequest(intentData); + stuckCount++; + break; + default: + //NOOP + break; + } + } + + for (IntentData intentData : store.getPendingData(true, periodMs)) { + resubmitPendingRequest(intentData); + stuckCount++; + } + + log.debug("Intent cleanup ran and resubmitted {} corrupt, {} failed, {} stuck, and {} pending intents", + corruptCount, failedCount, stuckCount, pendingCount); + } + + @Override + public void event(IntentEvent event) { + // this is the fast path for CORRUPT intents, retry on event notification. + //TODO we might consider using the timer to back off for subsequent retries + if (enabled && event.type() == IntentEvent.Type.CORRUPT) { + Key key = event.subject().key(); + if (store.isMaster(key)) { + IntentData data = store.getIntentData(event.subject().key()); + resubmitCorrupt(data, true); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCompilationException.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCompilationException.java new file mode 100644 index 00000000..ae93336c --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCompilationException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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.net.intent.impl; + +import org.onosproject.net.intent.IntentException; + +/** + * An exception thrown when a intent compilation fails. + */ +public class IntentCompilationException extends IntentException { + private static final long serialVersionUID = 235237603018210810L; + + public IntentCompilationException() { + super(); + } + + public IntentCompilationException(String message) { + super(message); + } + + public IntentCompilationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstallationException.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstallationException.java new file mode 100644 index 00000000..db21fe4a --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstallationException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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.net.intent.impl; + +import org.onosproject.net.intent.IntentException; + +/** + * An exception thrown when intent installation fails. + */ +public class IntentInstallationException extends IntentException { + private static final long serialVersionUID = 3720268258616014168L; + + public IntentInstallationException() { + super(); + } + + public IntentInstallationException(String message) { + super(message); + } + + public IntentInstallationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java new file mode 100644 index 00000000..4c828e77 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java @@ -0,0 +1,488 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import com.google.common.collect.ImmutableList; +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.onosproject.event.AbstractListenerManager; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentBatchDelegate; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.IntentListener; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentState; +import org.onosproject.net.intent.IntentStore; +import org.onosproject.net.intent.IntentStoreDelegate; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.impl.phase.FinalIntentProcessPhase; +import org.onosproject.net.intent.impl.phase.IntentProcessPhase; +import org.onosproject.net.intent.impl.phase.IntentWorker; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.intent.IntentState.*; +import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure; +import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.newInitialPhase; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * An implementation of intent service. + */ +@Component(immediate = true) +@Service +public class IntentManager + extends AbstractListenerManager<IntentEvent, IntentListener> + implements IntentService, IntentExtensionService { + + private static final Logger log = getLogger(IntentManager.class); + + public static final String INTENT_NULL = "Intent cannot be null"; + public static final String INTENT_ID_NULL = "Intent key cannot be null"; + + private static final int NUM_THREADS = 12; + + private static final EnumSet<IntentState> RECOMPILE + = EnumSet.of(INSTALL_REQ, FAILED, WITHDRAW_REQ); + private static final EnumSet<IntentState> WITHDRAW + = EnumSet.of(WITHDRAW_REQ, WITHDRAWING, WITHDRAWN); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ObjectiveTrackerService trackerService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowRuleService; + + private ExecutorService batchExecutor; + private ExecutorService workerExecutor; + + private final CompilerRegistry compilerRegistry = new CompilerRegistry(); + private final InternalIntentProcessor processor = new InternalIntentProcessor(); + private final IntentStoreDelegate delegate = new InternalStoreDelegate(); + private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate(); + private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate(); + private IdGenerator idGenerator; + + private final IntentAccumulator accumulator = new IntentAccumulator(batchDelegate); + + @Activate + public void activate() { + store.setDelegate(delegate); + trackerService.setDelegate(topoDelegate); + eventDispatcher.addSink(IntentEvent.class, listenerRegistry); + batchExecutor = newSingleThreadExecutor(groupedThreads("onos/intent", "batch")); + workerExecutor = newFixedThreadPool(NUM_THREADS, groupedThreads("onos/intent", "worker-%d")); + idGenerator = coreService.getIdGenerator("intent-ids"); + Intent.bindIdGenerator(idGenerator); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + trackerService.unsetDelegate(topoDelegate); + eventDispatcher.removeSink(IntentEvent.class); + batchExecutor.shutdown(); + workerExecutor.shutdown(); + Intent.unbindIdGenerator(idGenerator); + log.info("Stopped"); + } + + @Override + public void submit(Intent intent) { + checkPermission(INTENT_WRITE); + checkNotNull(intent, INTENT_NULL); + IntentData data = new IntentData(intent, IntentState.INSTALL_REQ, null); + store.addPending(data); + } + + @Override + public void withdraw(Intent intent) { + checkPermission(INTENT_WRITE); + checkNotNull(intent, INTENT_NULL); + IntentData data = new IntentData(intent, IntentState.WITHDRAW_REQ, null); + store.addPending(data); + } + + @Override + public void purge(Intent intent) { + checkPermission(INTENT_WRITE); + checkNotNull(intent, INTENT_NULL); + IntentData data = new IntentData(intent, IntentState.PURGE_REQ, null); + store.addPending(data); + } + + @Override + public Intent getIntent(Key key) { + checkPermission(INTENT_READ); + return store.getIntent(key); + } + + @Override + public Iterable<Intent> getIntents() { + checkPermission(INTENT_READ); + return store.getIntents(); + } + + @Override + public Iterable<IntentData> getIntentData() { + checkPermission(INTENT_READ); + return store.getIntentData(false, 0); + } + + @Override + public long getIntentCount() { + checkPermission(INTENT_READ); + return store.getIntentCount(); + } + + @Override + public IntentState getIntentState(Key intentKey) { + checkPermission(INTENT_READ); + checkNotNull(intentKey, INTENT_ID_NULL); + return store.getIntentState(intentKey); + } + + @Override + public List<Intent> getInstallableIntents(Key intentKey) { + checkPermission(INTENT_READ); + checkNotNull(intentKey, INTENT_ID_NULL); + return store.getInstallableIntents(intentKey); + } + + @Override + public boolean isLocal(Key intentKey) { + checkPermission(INTENT_READ); + return store.isMaster(intentKey); + } + + @Override + public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) { + compilerRegistry.registerCompiler(cls, compiler); + } + + @Override + public <T extends Intent> void unregisterCompiler(Class<T> cls) { + compilerRegistry.unregisterCompiler(cls); + } + + @Override + public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() { + return compilerRegistry.getCompilers(); + } + + @Override + public Iterable<Intent> getPending() { + checkPermission(INTENT_READ); + + return store.getPending(); + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements IntentStoreDelegate { + @Override + public void notify(IntentEvent event) { + post(event); + } + + @Override + public void process(IntentData data) { + accumulator.add(data); + } + + @Override + public void onUpdate(IntentData intentData) { + trackerService.trackIntent(intentData); + } + } + + private void buildAndSubmitBatches(Iterable<Key> intentKeys, + boolean compileAllFailed) { + // Attempt recompilation of the specified intents first. + for (Key key : intentKeys) { + Intent intent = store.getIntent(key); + if (intent == null) { + continue; + } + submit(intent); + } + + // If required, compile all currently failed intents. + for (Intent intent : getIntents()) { + IntentState state = getIntentState(intent.key()); + if ((compileAllFailed && RECOMPILE.contains(state)) + || intentAllowsPartialFailure(intent)) { + if (WITHDRAW.contains(state)) { + withdraw(intent); + } else { + submit(intent); + } + } + } + + //FIXME +// for (ApplicationId appId : batches.keySet()) { +// if (batchService.isLocalLeader(appId)) { +// execute(batches.get(appId).build()); +// } +// } + } + + // Topology change delegate + private class InternalTopoChangeDelegate implements TopologyChangeDelegate { + @Override + public void triggerCompile(Iterable<Key> intentKeys, + boolean compileAllFailed) { + buildAndSubmitBatches(intentKeys, compileAllFailed); + } + } + + private Future<FinalIntentProcessPhase> submitIntentData(IntentData data) { + IntentData current = store.getIntentData(data.key()); + IntentProcessPhase initial = newInitialPhase(processor, data, current); + return workerExecutor.submit(new IntentWorker(initial)); + } + + private class IntentBatchProcess implements Runnable { + + protected final Collection<IntentData> data; + + IntentBatchProcess(Collection<IntentData> data) { + this.data = checkNotNull(data); + } + + @Override + public void run() { + try { + /* + 1. wrap each intentdata in a runnable and submit + 2. wait for completion of all the work + 3. accumulate results and submit batch write of IntentData to store + (we can also try to update these individually) + */ + submitUpdates(waitForFutures(createIntentUpdates())); + } catch (Exception e) { + log.error("Error submitting batches:", e); + // FIXME incomplete Intents should be cleaned up + // (transition to FAILED, etc.) + + // the batch has failed + // TODO: maybe we should do more? + log.error("Walk the plank, matey..."); + //FIXME +// batchService.removeIntentOperations(data); + } + accumulator.ready(); + } + + private List<Future<FinalIntentProcessPhase>> createIntentUpdates() { + return data.stream() + .map(IntentManager.this::submitIntentData) + .collect(Collectors.toList()); + } + + private List<FinalIntentProcessPhase> waitForFutures(List<Future<FinalIntentProcessPhase>> futures) { + ImmutableList.Builder<FinalIntentProcessPhase> updateBuilder = ImmutableList.builder(); + for (Future<FinalIntentProcessPhase> future : futures) { + try { + updateBuilder.add(future.get()); + } catch (InterruptedException | ExecutionException e) { + //FIXME + log.warn("Future failed: {}", e); + } + } + return updateBuilder.build(); + } + + private void submitUpdates(List<FinalIntentProcessPhase> updates) { + store.batchWrite(updates.stream() + .map(FinalIntentProcessPhase::data) + .collect(Collectors.toList())); + } + } + + private class InternalBatchDelegate implements IntentBatchDelegate { + @Override + public void execute(Collection<IntentData> operations) { + log.debug("Execute {} operation(s).", operations.size()); + log.trace("Execute operations: {}", operations); + + // batchExecutor is single-threaded, so only one batch is in flight at a time + batchExecutor.execute(new IntentBatchProcess(operations)); + } + } + + private class InternalIntentProcessor implements IntentProcessor { + @Override + public List<Intent> compile(Intent intent, List<Intent> previousInstallables) { + return compilerRegistry.compile(intent, previousInstallables); + } + + @Override + public void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) { + IntentManager.this.apply(toUninstall, toInstall); + } + } + + private enum Direction { + ADD, + REMOVE + } + + private void applyIntentData(Optional<IntentData> intentData, + FlowRuleOperations.Builder builder, + Direction direction) { + if (!intentData.isPresent()) { + return; + } + IntentData data = intentData.get(); + + List<Intent> intentsToApply = data.installables(); + if (!intentsToApply.stream().allMatch(x -> x instanceof FlowRuleIntent)) { + throw new IllegalStateException("installable intents must be FlowRuleIntent"); + } + + if (direction == Direction.ADD) { + trackerService.addTrackedResources(data.key(), data.intent().resources()); + intentsToApply.forEach(installable -> + trackerService.addTrackedResources(data.key(), installable.resources())); + } else { + trackerService.removeTrackedResources(data.key(), data.intent().resources()); + intentsToApply.forEach(installable -> + trackerService.removeTrackedResources(data.intent().key(), + installable.resources())); + } + + // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so. + builder.newStage(); + + List<Collection<FlowRule>> stages = intentsToApply.stream() + .map(x -> (FlowRuleIntent) x) + .map(FlowRuleIntent::flowRules) + .collect(Collectors.toList()); + + for (Collection<FlowRule> rules : stages) { + if (direction == Direction.ADD) { + rules.forEach(builder::add); + } else { + rules.forEach(builder::remove); + } + } + + } + + private void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) { + // need to consider if FlowRuleIntent is only one as installable intent or not + + FlowRuleOperations.Builder builder = FlowRuleOperations.builder(); + applyIntentData(toUninstall, builder, Direction.REMOVE); + applyIntentData(toInstall, builder, Direction.ADD); + + FlowRuleOperations operations = builder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + if (toInstall.isPresent()) { + IntentData installData = toInstall.get(); + log.debug("Completed installing: {}", installData.key()); + installData.setState(INSTALLED); + store.write(installData); + } else if (toUninstall.isPresent()) { + IntentData uninstallData = toUninstall.get(); + log.debug("Completed withdrawing: {}", uninstallData.key()); + switch (uninstallData.request()) { + case INSTALL_REQ: + uninstallData.setState(FAILED); + break; + case WITHDRAW_REQ: + default: //TODO "default" case should not happen + uninstallData.setState(WITHDRAWN); + break; + } + store.write(uninstallData); + } + } + + @Override + public void onError(FlowRuleOperations ops) { + // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT) + if (toInstall.isPresent()) { + IntentData installData = toInstall.get(); + log.warn("Failed installation: {} {} on {}", + installData.key(), installData.intent(), ops); + installData.setState(CORRUPT); + installData.incrementErrorCount(); + store.write(installData); + } + // if toUninstall was cause of error, then CORRUPT (another job will clean this up) + if (toUninstall.isPresent()) { + IntentData uninstallData = toUninstall.get(); + log.warn("Failed withdrawal: {} {} on {}", + uninstallData.key(), uninstallData.intent(), ops); + uninstallData.setState(CORRUPT); + uninstallData.incrementErrorCount(); + store.write(uninstallData); + } + } + }); + + if (log.isTraceEnabled()) { + log.trace("applying intent {} -> {} with {} rules: {}", + toUninstall.isPresent() ? toUninstall.get().key() : "<empty>", + toInstall.isPresent() ? toInstall.get().key() : "<empty>", + operations.stages().stream().mapToLong(i -> i.size()).sum(), + operations.stages()); + } + + flowRuleService.apply(operations); + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentProcessor.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentProcessor.java new file mode 100644 index 00000000..5469c766 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 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.net.intent.impl; + +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentData; + +import java.util.List; +import java.util.Optional; + +/** + * A collection of methods to process an intent. + * + * This interface is public, but intended to be used only by IntentManager and + * IntentProcessPhase subclasses stored under phase package. + */ +public interface IntentProcessor { + + /** + * Compiles an intent recursively. + * + * @param intent intent + * @param previousInstallables previous intent installables + * @return result of compilation + */ + List<Intent> compile(Intent intent, List<Intent> previousInstallables); + + /** + * @param toUninstall Intent data describing flows to uninstall. + * @param toInstall Intent data describing flows to install. + */ + void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall); +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentRemovalException.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentRemovalException.java new file mode 100644 index 00000000..20530c0c --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentRemovalException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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.net.intent.impl; + +import org.onosproject.net.intent.IntentException; + +/** + * An exception thrown when intent removal failed. + */ +public class IntentRemovalException extends IntentException { + private static final long serialVersionUID = -5259226322037891951L; + + public IntentRemovalException() { + super(); + } + + public IntentRemovalException(String message) { + super(message); + } + + public IntentRemovalException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java new file mode 100644 index 00000000..5710aced --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java @@ -0,0 +1,457 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +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.ReferencePolicy; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.core.ApplicationId; +import org.onosproject.event.Event; +import org.onosproject.net.DeviceId; +import org.onosproject.net.ElementId; +import org.onosproject.net.HostId; +import org.onosproject.net.Link; +import org.onosproject.net.LinkKey; +import org.onosproject.net.NetworkResource; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +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.IntentData; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.PartitionEvent; +import org.onosproject.net.intent.PartitionEventListener; +import org.onosproject.net.intent.PartitionService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.resource.link.LinkResourceEvent; +import org.onosproject.net.resource.link.LinkResourceListener; +import org.onosproject.net.resource.link.LinkResourceService; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyListener; +import org.onosproject.net.topology.TopologyService; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Multimaps.synchronizedSetMultimap; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.onlab.util.Tools.groupedThreads; +import static org.onlab.util.Tools.isNullOrEmpty; +import static org.onosproject.net.LinkKey.linkKey; +import static org.onosproject.net.intent.IntentState.INSTALLED; +import static org.onosproject.net.intent.IntentState.INSTALLING; +import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; +import static org.onosproject.net.link.LinkEvent.Type.LINK_UPDATED; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Entity responsible for tracking installed flows and for monitoring topology + * events to determine what flows are affected by topology changes. + */ +@Component(immediate = true) +@Service +public class ObjectiveTracker implements ObjectiveTrackerService { + + private final Logger log = getLogger(getClass()); + + private final ConcurrentMap<Key, Intent> intents = Maps.newConcurrentMap(); + + private final SetMultimap<LinkKey, Key> intentsByLink = + //TODO this could be slow as a point of synchronization + synchronizedSetMultimap(HashMultimap.<LinkKey, Key>create()); + + private final SetMultimap<ElementId, Key> intentsByDevice = + synchronizedSetMultimap(HashMultimap.<ElementId, Key>create()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkResourceService resourceManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, + policy = ReferencePolicy.DYNAMIC) + protected IntentService intentService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PartitionService partitionService; + + private ExecutorService executorService = + newSingleThreadExecutor(groupedThreads("onos/intent", "objectivetracker")); + private ScheduledExecutorService executor = Executors + .newScheduledThreadPool(1); + + private TopologyListener listener = new InternalTopologyListener(); + private LinkResourceListener linkResourceListener = + new InternalLinkResourceListener(); + private DeviceListener deviceListener = new InternalDeviceListener(); + private HostListener hostListener = new InternalHostListener(); + private PartitionEventListener partitionListener = new InternalPartitionListener(); + private TopologyChangeDelegate delegate; + + protected final AtomicBoolean updateScheduled = new AtomicBoolean(false); + + @Activate + public void activate() { + topologyService.addListener(listener); + resourceManager.addListener(linkResourceListener); + deviceService.addListener(deviceListener); + hostService.addListener(hostListener); + partitionService.addListener(partitionListener); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + topologyService.removeListener(listener); + resourceManager.removeListener(linkResourceListener); + deviceService.removeListener(deviceListener); + hostService.removeListener(hostListener); + partitionService.removeListener(partitionListener); + log.info("Stopped"); + } + + protected void bindIntentService(IntentService service) { + if (intentService == null) { + intentService = service; + } + } + + protected void unbindIntentService(IntentService service) { + if (intentService == service) { + intentService = null; + } + } + + @Override + public void setDelegate(TopologyChangeDelegate delegate) { + checkNotNull(delegate, "Delegate cannot be null"); + checkArgument(this.delegate == null || this.delegate == delegate, + "Another delegate already set"); + this.delegate = delegate; + } + + @Override + public void unsetDelegate(TopologyChangeDelegate delegate) { + checkArgument(this.delegate == delegate, "Not the current delegate"); + this.delegate = null; + } + + @Override + public void addTrackedResources(Key intentKey, + Collection<NetworkResource> resources) { + for (NetworkResource resource : resources) { + if (resource instanceof Link) { + intentsByLink.put(linkKey((Link) resource), intentKey); + } else if (resource instanceof ElementId) { + intentsByDevice.put((ElementId) resource, intentKey); + } + } + } + + @Override + public void removeTrackedResources(Key intentKey, + Collection<NetworkResource> resources) { + for (NetworkResource resource : resources) { + if (resource instanceof Link) { + intentsByLink.remove(linkKey((Link) resource), intentKey); + } else if (resource instanceof ElementId) { + intentsByDevice.remove(resource, intentKey); + } + } + } + + @Override + public void trackIntent(IntentData intentData) { + + //NOTE: This will be called for intents that are being added to the store + // locally (i.e. every intent update) + + Key key = intentData.key(); + Intent intent = intentData.intent(); + boolean isLocal = intentService.isLocal(key); + boolean isInstalled = intentData.state() == INSTALLING || + intentData.state() == INSTALLED; + List<Intent> installables = intentData.installables(); + + if (log.isTraceEnabled()) { + log.trace("intent {}, old: {}, new: {}, installableCount: {}, resourceCount: {}", + key, + intentsByDevice.values().contains(key), + isLocal && isInstalled, + installables.size(), + intent.resources().size() + + installables.stream() + .mapToLong(i -> i.resources().size()).sum()); + } + + if (isNullOrEmpty(installables) && intentData.state() == INSTALLED) { + log.warn("Intent {} is INSTALLED with no installables", key); + } + + // FIXME Intents will be added 3 times (once directly using addTracked, + // then when installing and when installed) + if (isLocal && isInstalled) { + addTrackedResources(key, intent.resources()); + for (Intent installable : installables) { + addTrackedResources(key, installable.resources()); + } + // FIXME check all resources against current topo service(s); recompile if necessary + } else { + removeTrackedResources(key, intent.resources()); + for (Intent installable : installables) { + removeTrackedResources(key, installable.resources()); + } + } + } + + // Internal re-actor to topology change events. + private class InternalTopologyListener implements TopologyListener { + @Override + public void event(TopologyEvent event) { + executorService.execute(new TopologyChangeHandler(event)); + } + } + + // Re-dispatcher of topology change events. + private class TopologyChangeHandler implements Runnable { + + private final TopologyEvent event; + + TopologyChangeHandler(TopologyEvent event) { + this.event = event; + } + + @Override + public void run() { + // If there is no delegate, why bother? Just bail. + if (delegate == null) { + return; + } + + if (event.reasons() == null || event.reasons().isEmpty()) { + delegate.triggerCompile(Collections.emptySet(), true); + + } else { + Set<Key> intentsToRecompile = new HashSet<>(); + boolean dontRecompileAllFailedIntents = true; + + // Scan through the list of reasons and keep accruing all + // intents that need to be recompiled. + for (Event reason : event.reasons()) { + if (reason instanceof LinkEvent) { + LinkEvent linkEvent = (LinkEvent) reason; + final LinkKey linkKey = linkKey(linkEvent.subject()); + synchronized (intentsByLink) { + Set<Key> intentKeys = intentsByLink.get(linkKey); + log.debug("recompile triggered by LinkEvent {} ({}) for {}", + linkKey, linkEvent.type(), intentKeys); + intentsToRecompile.addAll(intentKeys); + } + dontRecompileAllFailedIntents = dontRecompileAllFailedIntents && + (linkEvent.type() == LINK_REMOVED || + (linkEvent.type() == LINK_UPDATED && + linkEvent.subject().isDurable())); + } + } + delegate.triggerCompile(intentsToRecompile, !dontRecompileAllFailedIntents); + } + } + } + + /** + * Internal re-actor to resource available events. + */ + private class InternalLinkResourceListener implements LinkResourceListener { + @Override + public void event(LinkResourceEvent event) { + executorService.execute(new ResourceAvailableHandler(event)); + } + } + + /* + * Re-dispatcher of resource available events. + */ + private class ResourceAvailableHandler implements Runnable { + + private final LinkResourceEvent event; + + ResourceAvailableHandler(LinkResourceEvent event) { + this.event = event; + } + + @Override + public void run() { + // If there is no delegate, why bother? Just bail. + if (delegate == null) { + return; + } + + delegate.triggerCompile(Collections.emptySet(), true); + } + } + + //TODO consider adding flow rule event tracking + + private void updateTrackedResources(ApplicationId appId, boolean track) { + if (intentService == null) { + log.warn("Intent service is not bound yet"); + return; + } + intentService.getIntents().forEach(intent -> { + if (intent.appId().equals(appId)) { + Key key = intent.key(); + Collection<NetworkResource> resources = Lists.newArrayList(); + intentService.getInstallableIntents(key).stream() + .map(installable -> installable.resources()) + .forEach(resources::addAll); + if (track) { + addTrackedResources(key, resources); + } else { + removeTrackedResources(key, resources); + } + } + }); + } + + /* + * Re-dispatcher of device and host events. + */ + private class DeviceAvailabilityHandler implements Runnable { + + private final ElementId id; + private final boolean available; + + DeviceAvailabilityHandler(ElementId id, boolean available) { + this.id = checkNotNull(id); + this.available = available; + } + + @Override + public void run() { + // If there is no delegate, why bother? Just bail. + if (delegate == null) { + return; + } + + // TODO should we recompile on available==true? + delegate.triggerCompile(intentsByDevice.get(id), available); + } + } + + + private class InternalDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + DeviceEvent.Type type = event.type(); + switch (type) { + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + case DEVICE_REMOVED: + case DEVICE_SUSPENDED: + case DEVICE_UPDATED: + DeviceId id = event.subject().id(); + // TODO we need to check whether AVAILABILITY_CHANGED means up or down + boolean available = (type == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED || + type == DeviceEvent.Type.DEVICE_ADDED || + type == DeviceEvent.Type.DEVICE_UPDATED); + executorService.execute(new DeviceAvailabilityHandler(id, available)); + break; + case PORT_ADDED: + case PORT_REMOVED: + case PORT_UPDATED: + case PORT_STATS_UPDATED: + default: + // Don't handle port events for now + break; + } + } + } + + private class InternalHostListener implements HostListener { + @Override + public void event(HostEvent event) { + HostId id = event.subject().id(); + HostEvent.Type type = event.type(); + boolean available = (type == HostEvent.Type.HOST_ADDED); + executorService.execute(new DeviceAvailabilityHandler(id, available)); + } + } + + protected void doIntentUpdate() { + updateScheduled.set(false); + if (intentService == null) { + log.warn("Intent service is not bound yet"); + return; + } + try { + //FIXME very inefficient + for (IntentData intentData : intentService.getIntentData()) { + try { + trackIntent(intentData); + } catch (NullPointerException npe) { + log.warn("intent error {}", intentData.key(), npe); + } + } + } catch (Exception e) { + log.warn("Exception caught during update task", e); + } + } + + private void scheduleIntentUpdate(int afterDelaySec) { + if (updateScheduled.compareAndSet(false, true)) { + executor.schedule(this::doIntentUpdate, afterDelaySec, TimeUnit.SECONDS); + } + } + + private final class InternalPartitionListener implements PartitionEventListener { + @Override + public void event(PartitionEvent event) { + log.debug("got message {}", event.subject()); + scheduleIntentUpdate(1); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTrackerService.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTrackerService.java new file mode 100644 index 00000000..b7d367d7 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTrackerService.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import org.onosproject.net.NetworkResource; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.Key; + +import java.util.Collection; + +/** + * Auxiliary service for tracking intent path flows and for notifying the + * intent service of environment changes via topology change delegate. + */ +public interface ObjectiveTrackerService { + + /** + * Sets a topology change delegate. + * + * @param delegate topology change delegate + */ + void setDelegate(TopologyChangeDelegate delegate); + + /** + * Unsets topology change delegate. + * + * @param delegate topology change delegate + */ + void unsetDelegate(TopologyChangeDelegate delegate); + + /** + * Adds a path flow to be tracked. + * + * @param intentKey intent identity on whose behalf the path is being tracked + * @param resources resources to track + */ + // TODO consider using the IntentData here rather than just the key + void addTrackedResources(Key intentKey, + Collection<NetworkResource> resources); + + /** + * Removes a path flow to be tracked. + * + * @param intentKey intent identity on whose behalf the path is being tracked + * @param resources resources to stop tracking + */ + void removeTrackedResources(Key intentKey, + Collection<NetworkResource> resources); + + /** + * Submits the specified intent data to be tracked. + * + * @param intentData intent data object to be tracked + */ + void trackIntent(IntentData intentData); +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/PathNotFoundException.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/PathNotFoundException.java new file mode 100644 index 00000000..c06e9fd2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/PathNotFoundException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import com.google.common.base.MoreObjects; +import org.onosproject.net.ElementId; +import org.onosproject.net.intent.IntentException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An exception thrown when a path is not found. + */ +public class PathNotFoundException extends IntentException { + private static final long serialVersionUID = -2087045731049914733L; + + private final ElementId source; + private final ElementId destination; + + public PathNotFoundException(ElementId source, ElementId destination) { + super(String.format("No path from %s to %s", source, destination)); + this.source = checkNotNull(source); + this.destination = checkNotNull(destination); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("source", source) + .add("destination", destination) + .toString(); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/TopologyChangeDelegate.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/TopologyChangeDelegate.java new file mode 100644 index 00000000..49b114d5 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/TopologyChangeDelegate.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import org.onosproject.net.intent.Key; + +/** + * Auxiliary delegate for integration of intent manager and flow trackerService. + */ +public interface TopologyChangeDelegate { + + /** + * Notifies that topology has changed in such a way that the specified + * intents should be recompiled. If the {@code compileAllFailed} parameter + * is true, then all intents in {@link org.onosproject.net.intent.IntentState#FAILED} + * state should be compiled as well. + * + * @param intentIds intents that should be recompiled + * @param compileAllFailed true implies full compile of all failed intents + * is required; false for selective recompile only + */ + void triggerCompile(Iterable<Key> intentIds, boolean compileAllFailed); + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java new file mode 100644 index 00000000..6de4cfdc --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.net.ElementId; +import org.onosproject.net.Path; +import org.onosproject.net.intent.ConnectivityIntent; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.impl.PathNotFoundException; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.resource.link.LinkResourceService; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.PathService; +import org.onosproject.net.topology.TopologyEdge; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * Base class for compilers of various + * {@link org.onosproject.net.intent.ConnectivityIntent connectivity intents}. + */ +@Component(immediate = true) +public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent> + implements IntentCompiler<T> { + + private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PathService pathService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkResourceService resourceService; + + /** + * Returns an edge-weight capable of evaluating links on the basis of the + * specified constraints. + * + * @param constraints path constraints + * @return edge-weight function + */ + protected LinkWeight weight(List<Constraint> constraints) { + return new ConstraintBasedLinkWeight(constraints); + } + + /** + * Validates the specified path against the given constraints. + * + * @param path path to be checked + * @param constraints path constraints + * @return true if the path passes all constraints + */ + protected boolean checkPath(Path path, List<Constraint> constraints) { + for (Constraint constraint : constraints) { + if (!constraint.validate(path, resourceService)) { + return false; + } + } + return true; + } + + /** + * Computes a path between two ConnectPoints. + * + * @param intent intent on which behalf path is being computed + * @param one start of the path + * @param two end of the path + * @return Path between the two + * @throws PathNotFoundException if a path cannot be found + */ + protected Path getPath(ConnectivityIntent intent, + ElementId one, ElementId two) { + Set<Path> paths = pathService.getPaths(one, two, weight(intent.constraints())); + final List<Constraint> constraints = intent.constraints(); + ImmutableList<Path> filtered = FluentIterable.from(paths) + .filter(path -> checkPath(path, constraints)) + .toList(); + if (filtered.isEmpty()) { + throw new PathNotFoundException(one, two); + } + // TODO: let's be more intelligent about this eventually + return filtered.iterator().next(); + } + + /** + * Edge-weight capable of evaluating link cost using a set of constraints. + */ + protected class ConstraintBasedLinkWeight implements LinkWeight { + + private final List<Constraint> constraints; + + /** + * Creates a new edge-weight function capable of evaluating links + * on the basis of the specified constraints. + * + * @param constraints path constraints + */ + ConstraintBasedLinkWeight(List<Constraint> constraints) { + if (constraints == null) { + this.constraints = Collections.emptyList(); + } else { + this.constraints = ImmutableList.copyOf(constraints); + } + } + + @Override + public double weight(TopologyEdge edge) { + if (!constraints.iterator().hasNext()) { + return 1.0; + } + + // iterate over all constraints in order and return the weight of + // the first one with fast fail over the first failure + Iterator<Constraint> it = constraints.iterator(); + + double cost = it.next().cost(edge.link(), resourceService); + while (it.hasNext() && cost > 0) { + if (it.next().cost(edge.link(), resourceService) < 0) { + return -1; + } + } + return cost; + + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java new file mode 100644 index 00000000..41168258 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +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.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Host; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.host.HostService; +import org.onosproject.net.intent.HostToHostIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.PathIntent; +import org.onosproject.net.intent.constraint.AsymmetricPathConstraint; +import org.onosproject.net.resource.link.LinkResourceAllocations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static org.onosproject.net.flow.DefaultTrafficSelector.builder; + +/** + * A intent compiler for {@link HostToHostIntent}. + */ +@Component(immediate = true) +public class HostToHostIntentCompiler + extends ConnectivityIntentCompiler<HostToHostIntent> { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Activate + public void activate() { + intentManager.registerCompiler(HostToHostIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(HostToHostIntent.class); + } + + @Override + public List<Intent> compile(HostToHostIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + boolean isAsymmetric = intent.constraints().contains(new AsymmetricPathConstraint()); + Path pathOne = getPath(intent, intent.one(), intent.two()); + Path pathTwo = isAsymmetric ? + getPath(intent, intent.two(), intent.one()) : invertPath(pathOne); + + Host one = hostService.getHost(intent.one()); + Host two = hostService.getHost(intent.two()); + + return Arrays.asList(createPathIntent(pathOne, one, two, intent), + createPathIntent(pathTwo, two, one, intent)); + } + + // Inverts the specified path. This makes an assumption that each link in + // the path has a reverse link available. Under most circumstances, this + // assumption will hold. + private Path invertPath(Path path) { + List<Link> reverseLinks = new ArrayList<>(path.links().size()); + for (Link link : path.links()) { + reverseLinks.add(0, reverseLink(link)); + } + return new DefaultPath(path.providerId(), reverseLinks, path.cost()); + } + + // Produces a reverse variant of the specified link. + private Link reverseLink(Link link) { + return new DefaultLink(link.providerId(), link.dst(), link.src(), + link.type(), link.state(), link.isDurable()); + } + + // Creates a path intent from the specified path and original connectivity intent. + private Intent createPathIntent(Path path, Host src, Host dst, + HostToHostIntent intent) { + TrafficSelector selector = builder(intent.selector()) + .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build(); + return PathIntent.builder() + .appId(intent.appId()) + .selector(selector) + .treatment(intent.treatment()) + .path(path) + .constraints(intent.constraints()) + .priority(intent.priority()) + .build(); + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java new file mode 100644 index 00000000..76c5736d --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import com.google.common.collect.HashMultimap; +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.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.LinkCollectionIntent; +import org.onosproject.net.resource.link.LinkResourceAllocations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Component(immediate = true) +public class LinkCollectionIntentCompiler implements IntentCompiler<LinkCollectionIntent> { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + private ApplicationId appId; + + @Activate + public void activate() { + appId = coreService.registerApplication("org.onosproject.net.intent"); + intentManager.registerCompiler(LinkCollectionIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(LinkCollectionIntent.class); + } + + @Override + public List<Intent> compile(LinkCollectionIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + SetMultimap<DeviceId, PortNumber> inputPorts = HashMultimap.create(); + SetMultimap<DeviceId, PortNumber> outputPorts = HashMultimap.create(); + + for (Link link : intent.links()) { + inputPorts.put(link.dst().deviceId(), link.dst().port()); + outputPorts.put(link.src().deviceId(), link.src().port()); + } + + for (ConnectPoint ingressPoint : intent.ingressPoints()) { + inputPorts.put(ingressPoint.deviceId(), ingressPoint.port()); + } + + for (ConnectPoint egressPoint : intent.egressPoints()) { + outputPorts.put(egressPoint.deviceId(), egressPoint.port()); + } + + List<FlowRule> rules = new ArrayList<>(); + for (DeviceId deviceId: outputPorts.keys()) { + rules.addAll(createRules(intent, deviceId, inputPorts.get(deviceId), outputPorts.get(deviceId))); + } + return Collections.singletonList(new FlowRuleIntent(appId, rules, intent.resources())); + } + + private List<FlowRule> createRules(LinkCollectionIntent intent, DeviceId deviceId, + Set<PortNumber> inPorts, Set<PortNumber> outPorts) { + Set<PortNumber> ingressPorts = intent.ingressPoints().stream() + .filter(point -> point.deviceId().equals(deviceId)) + .map(ConnectPoint::port) + .collect(Collectors.toSet()); + + TrafficTreatment.Builder defaultTreatmentBuilder = DefaultTrafficTreatment.builder(); + outPorts.stream() + .forEach(defaultTreatmentBuilder::setOutput); + TrafficTreatment defaultTreatment = defaultTreatmentBuilder.build(); + + TrafficTreatment.Builder ingressTreatmentBuilder = DefaultTrafficTreatment.builder(intent.treatment()); + outPorts.stream() + .forEach(ingressTreatmentBuilder::setOutput); + TrafficTreatment ingressTreatment = ingressTreatmentBuilder.build(); + + List<FlowRule> rules = new ArrayList<>(inPorts.size()); + for (PortNumber inPort: inPorts) { + TrafficSelector selector = DefaultTrafficSelector.builder(intent.selector()).matchInPort(inPort).build(); + TrafficTreatment treatment; + if (ingressPorts.contains(inPort)) { + treatment = ingressTreatment; + } else { + treatment = defaultTreatment; + } + + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector) + .withTreatment(treatment) + .withPriority(intent.priority()) + .fromApp(appId) + .makePermanent() + .build(); + rules.add(rule); + } + + return rules; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java new file mode 100644 index 00000000..609f9a34 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java @@ -0,0 +1,91 @@ +package org.onosproject.net.intent.impl.compiler; + +import static java.util.Arrays.asList; +import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.MplsIntent; +import org.onosproject.net.intent.MplsPathIntent; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.resource.link.LinkResourceAllocations; + + +@Component(immediate = true) +public class MplsIntentCompiler extends ConnectivityIntentCompiler<MplsIntent> { + + // TODO: use off-the-shell core provider ID + private static final ProviderId PID = + new ProviderId("core", "org.onosproject.core", true); + // TODO: consider whether the default cost is appropriate or not + public static final int DEFAULT_COST = 1; + + + @Activate + public void activate() { + intentManager.registerCompiler(MplsIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(MplsIntent.class); + } + + @Override + public List<Intent> compile(MplsIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + ConnectPoint ingressPoint = intent.ingressPoint(); + ConnectPoint egressPoint = intent.egressPoint(); + + if (ingressPoint.deviceId().equals(egressPoint.deviceId())) { + List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false)); + return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST), intent)); + } + + List<Link> links = new ArrayList<>(); + Path path = getPath(intent, ingressPoint.deviceId(), + egressPoint.deviceId()); + + links.add(createEdgeLink(ingressPoint, true)); + links.addAll(path.links()); + + links.add(createEdgeLink(egressPoint, false)); + + return asList(createPathIntent(new DefaultPath(PID, links, path.cost(), + path.annotations()), intent)); + } + + /** + * Creates a path intent from the specified path and original + * connectivity intent. + * + * @param path path to create an intent for + * @param intent original intent + */ + private Intent createPathIntent(Path path, + MplsIntent intent) { + return MplsPathIntent.builder() + .appId(intent.appId()) + .selector(intent.selector()) + .treatment(intent.treatment()) + .path(path) + .ingressLabel(intent.ingressLabel()) + .egressLabel(intent.egressLabel()) + .constraints(intent.constraints()) + .priority(intent.priority()) + .build(); + } + + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompiler.java new file mode 100644 index 00000000..5fd1c85d --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompiler.java @@ -0,0 +1,291 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import com.google.common.collect.Sets; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.EthType; +import org.onlab.packet.Ethernet; +import org.onlab.packet.VlanId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +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.EthTypeCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.MplsPathIntent; +import org.onosproject.net.link.LinkStore; +import org.onosproject.net.resource.link.DefaultLinkResourceRequest; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.resource.link.LinkResourceRequest; +import org.onosproject.net.resource.link.LinkResourceService; +import org.onosproject.net.resource.link.MplsLabel; +import org.onosproject.net.resource.link.MplsLabelResourceAllocation; +import org.onosproject.net.resource.ResourceAllocation; +import org.onosproject.net.resource.ResourceType; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +@Component(immediate = true) +public class MplsPathIntentCompiler implements IntentCompiler<MplsPathIntent> { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentExtensionService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkResourceService resourceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkStore linkStore; + + protected ApplicationId appId; + + @Override + public List<Intent> compile(MplsPathIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + LinkResourceAllocations allocations = assignMplsLabel(intent); + List<FlowRule> rules = generateRules(intent, allocations); + + return Collections.singletonList(new FlowRuleIntent(appId, rules, intent.resources())); + } + + @Activate + public void activate() { + appId = coreService.registerApplication("org.onosproject.net.intent"); + intentExtensionService.registerCompiler(MplsPathIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentExtensionService.unregisterCompiler(MplsPathIntent.class); + } + + private LinkResourceAllocations assignMplsLabel(MplsPathIntent intent) { + // TODO: do it better... Suggestions? + Set<Link> linkRequest = Sets.newHashSetWithExpectedSize(intent.path() + .links().size() - 2); + for (int i = 1; i <= intent.path().links().size() - 2; i++) { + Link link = intent.path().links().get(i); + linkRequest.add(link); + // add the inverse link. I want that the label is reserved both for + // the direct and inverse link + linkRequest.add(linkStore.getLink(link.dst(), link.src())); + } + + LinkResourceRequest.Builder request = DefaultLinkResourceRequest + .builder(intent.id(), linkRequest).addMplsRequest(); + LinkResourceAllocations reqMpls = resourceService + .requestResources(request.build()); + return reqMpls; + } + + private MplsLabel getMplsLabel(LinkResourceAllocations allocations, Link link) { + for (ResourceAllocation allocation : allocations + .getResourceAllocation(link)) { + if (allocation.type() == ResourceType.MPLS_LABEL) { + return ((MplsLabelResourceAllocation) allocation).mplsLabel(); + + } + } + log.warn("MPLS label was not assigned successfully"); + return null; + } + + private List<FlowRule> generateRules(MplsPathIntent intent, + LinkResourceAllocations allocations) { + + Iterator<Link> links = intent.path().links().iterator(); + Link srcLink = links.next(); + ConnectPoint prev = srcLink.dst(); + + Link link = links.next(); + // List of flow rules to be installed + List<FlowRule> rules = new LinkedList<>(); + + // Ingress traffic + // Get the new MPLS label + MplsLabel mpls = getMplsLabel(allocations, link); + checkNotNull(mpls); + MplsLabel prevLabel = mpls; + rules.add(ingressFlow(prev.port(), link, intent, mpls)); + + prev = link.dst(); + + while (links.hasNext()) { + + link = links.next(); + + if (links.hasNext()) { + // Transit traffic + // Get the new MPLS label + mpls = getMplsLabel(allocations, link); + checkNotNull(mpls); + rules.add(transitFlow(prev.port(), link, intent, + prevLabel, mpls)); + prevLabel = mpls; + + } else { + // Egress traffic + rules.add(egressFlow(prev.port(), link, intent, + prevLabel)); + } + + prev = link.dst(); + } + return rules; + } + + private FlowRule ingressFlow(PortNumber inPort, Link link, + MplsPathIntent intent, MplsLabel label) { + + TrafficSelector.Builder ingressSelector = DefaultTrafficSelector + .builder(intent.selector()); + TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(); + ingressSelector.matchInPort(inPort); + + if (intent.ingressLabel().isPresent()) { + ingressSelector.matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(intent.ingressLabel().get()); + + // Swap the MPLS label + treat.setMpls(label.label()); + } else { + // Push and set the MPLS label + treat.pushMpls().setMpls(label.label()); + } + // Add the output action + treat.setOutput(link.src().port()); + + return createFlowRule(intent, link.src().deviceId(), ingressSelector.build(), treat.build()); + } + + private FlowRule transitFlow(PortNumber inPort, Link link, + MplsPathIntent intent, + MplsLabel prevLabel, + MplsLabel outLabel) { + + // Ignore the ingress Traffic Selector and use only the MPLS label + // assigned in the previous link + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchInPort(inPort).matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(prevLabel.label()); + TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(); + + // Set the new label only if the label on the packet is + // different + if (!prevLabel.equals(outLabel)) { + treat.setMpls(outLabel.label()); + } + + treat.setOutput(link.src().port()); + return createFlowRule(intent, link.src().deviceId(), selector.build(), treat.build()); + } + + private FlowRule egressFlow(PortNumber inPort, Link link, + MplsPathIntent intent, + MplsLabel prevLabel) { + // egress point: either set the egress MPLS label or pop the + // MPLS label based on the intent annotations + + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchInPort(inPort).matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(prevLabel.label()); + + // apply the intent's treatments + TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(intent + .treatment()); + + // check if the treatement is popVlan or setVlan (rewrite), + // than selector needs to match any VlanId + for (Instruction instruct : intent.treatment().allInstructions()) { + if (instruct instanceof L2ModificationInstruction) { + L2ModificationInstruction l2Mod = (L2ModificationInstruction) instruct; + if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) { + break; + } + if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP || + l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_ID) { + selector.matchVlanId(VlanId.ANY); + } + } + } + + if (intent.egressLabel().isPresent()) { + treat.setMpls(intent.egressLabel().get()); + } else { + treat.popMpls(outputEthType(intent.selector())); + } + treat.setOutput(link.src().port()); + return createFlowRule(intent, link.src().deviceId(), + selector.build(), treat.build()); + } + + protected FlowRule createFlowRule(MplsPathIntent intent, DeviceId deviceId, + TrafficSelector selector, TrafficTreatment treat) { + return DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector) + .withTreatment(treat) + .withPriority(intent.priority()) + .fromApp(appId) + .makePermanent() + .build(); + } + + // if the ingress ethertype is defined, the egress traffic + // will be use that value, otherwise the IPv4 ethertype is used. + private EthType outputEthType(TrafficSelector selector) { + Criterion c = selector.getCriterion(Criterion.Type.ETH_TYPE); + if (c != null && c instanceof EthTypeCriterion) { + EthTypeCriterion ethertype = (EthTypeCriterion) c; + return ethertype.ethType(); + } else { + return EthType.EtherType.IPV4.ethType(); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java new file mode 100644 index 00000000..06d0e9a2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java @@ -0,0 +1,151 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentException; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.LinkCollectionIntent; +import org.onosproject.net.intent.MultiPointToSinglePointIntent; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.topology.PathService; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure; + + +/** + * An intent compiler for + * {@link org.onosproject.net.intent.MultiPointToSinglePointIntent}. + */ +@Component(immediate = true) +public class MultiPointToSinglePointIntentCompiler + implements IntentCompiler<MultiPointToSinglePointIntent> { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PathService pathService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Activate + public void activate() { + intentManager.registerCompiler(MultiPointToSinglePointIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(PointToPointIntent.class); + } + + @Override + public List<Intent> compile(MultiPointToSinglePointIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + Map<DeviceId, Link> links = new HashMap<>(); + ConnectPoint egressPoint = intent.egressPoint(); + + final boolean allowMissingPaths = intentAllowsPartialFailure(intent); + boolean partialTree = false; + boolean anyMissingPaths = false; + for (ConnectPoint ingressPoint : intent.ingressPoints()) { + if (ingressPoint.deviceId().equals(egressPoint.deviceId())) { + if (deviceService.isAvailable(ingressPoint.deviceId())) { + partialTree = true; + } else { + anyMissingPaths = true; + } + + continue; + } + + Path path = getPath(ingressPoint, intent.egressPoint()); + if (path != null) { + partialTree = true; + + for (Link link : path.links()) { + if (links.containsKey(link.dst().deviceId())) { + // We've already reached the existing tree with the first + // part of this path. Add the merging point with different + // incoming port, but don't add the remainder of the path + // in case it differs from the path we already have. + links.put(link.src().deviceId(), link); + break; + } + links.put(link.src().deviceId(), link); + } + } else { + anyMissingPaths = true; + } + } + + if (!partialTree) { + throw new IntentException("Could not find any paths between ingress and egress points."); + } else if (!allowMissingPaths && anyMissingPaths) { + throw new IntentException("Missing some paths between ingress and egress ports."); + } + + Intent result = LinkCollectionIntent.builder() + .appId(intent.appId()) + .selector(intent.selector()) + .treatment(intent.treatment()) + .links(Sets.newHashSet(links.values())) + .ingressPoints(intent.ingressPoints()) + .egressPoints(ImmutableSet.of(intent.egressPoint())) + .priority(intent.priority()) + .constraints(intent.constraints()) + .build(); + + return Collections.singletonList(result); + } + + /** + * Computes a path between two ConnectPoints. + * + * @param one start of the path + * @param two end of the path + * @return Path between the two + */ + private Path getPath(ConnectPoint one, ConnectPoint two) { + Set<Path> paths = pathService.getPaths(one.deviceId(), two.deviceId()); + if (paths.isEmpty()) { + return null; + } + // TODO: let's be more intelligent about this eventually + return paths.iterator().next(); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java new file mode 100644 index 00000000..99f58df7 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java @@ -0,0 +1,372 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import com.google.common.collect.Sets; +import org.apache.commons.lang3.tuple.Pair; +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.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.util.Tools; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.OchPort; +import org.onosproject.net.OduCltPort; +import org.onosproject.net.OduSignalType; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.IntentId; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.OpticalCircuitIntent; +import org.onosproject.net.intent.OpticalConnectivityIntent; +import org.onosproject.net.intent.impl.IntentCompilationException; +import org.onosproject.net.resource.device.DeviceResourceService; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * An intent compiler for {@link org.onosproject.net.intent.OpticalCircuitIntent}. + */ +@Component(immediate = true) +public class OpticalCircuitIntentCompiler implements IntentCompiler<OpticalCircuitIntent> { + + private static final Logger log = LoggerFactory.getLogger(OpticalCircuitIntentCompiler.class); + + private static final int DEFAULT_MAX_CAPACITY = 10; + + @Property(name = "maxCapacity", intValue = DEFAULT_MAX_CAPACITY, + label = "Maximum utilization of an optical connection.") + + private int maxCapacity = DEFAULT_MAX_CAPACITY; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceResourceService deviceResourceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentService intentService; + + private ApplicationId appId; + + @Modified + public void modified(ComponentContext context) { + Dictionary properties = context.getProperties(); + + //TODO for reduction check if the new capacity is smaller than the size of the current mapping + String propertyString = Tools.get(properties, "maxCapacity"); + + //Ignore if propertyString is empty + if (!propertyString.isEmpty()) { + try { + int temp = Integer.parseInt(propertyString); + //Ensure value is non-negative but allow zero as a way to shutdown the link + if (temp >= 0) { + maxCapacity = temp; + } + } catch (NumberFormatException e) { + //Malformed arguments lead to no change of value (user should be notified of error) + log.error("The value '{}' for maxCapacity was not parsable as an integer.", propertyString, e); + } + } else { + //Notify of empty value but do not return (other properties will also go in this function) + log.error("The value for maxCapacity was set to an empty value."); + } + + } + + @Activate + public void activate(ComponentContext context) { + appId = coreService.registerApplication("org.onosproject.net.intent"); + intentManager.registerCompiler(OpticalCircuitIntent.class, this); + cfgService.registerProperties(getClass()); + modified(context); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(OpticalCircuitIntent.class); + cfgService.unregisterProperties(getClass(), false); + } + + @Override + public List<Intent> compile(OpticalCircuitIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + // Check if ports are OduClt ports + ConnectPoint src = intent.getSrc(); + ConnectPoint dst = intent.getDst(); + Port srcPort = deviceService.getPort(src.deviceId(), src.port()); + Port dstPort = deviceService.getPort(dst.deviceId(), dst.port()); + checkArgument(srcPort instanceof OduCltPort); + checkArgument(dstPort instanceof OduCltPort); + + log.debug("Compiling optical circuit intent between {} and {}", src, dst); + + // Reserve OduClt ports + if (!deviceResourceService.requestPorts(Sets.newHashSet(srcPort, dstPort), intent)) { + throw new IntentCompilationException("Unable to reserve ports for intent " + intent); + } + + LinkedList<Intent> intents = new LinkedList<>(); + + FlowRuleIntent circuitIntent; + OpticalConnectivityIntent connIntent = findOpticalConnectivityIntent(intent); + + // Create optical connectivity intent if needed + if (connIntent == null) { + // Find OCh ports with available resources + Pair<OchPort, OchPort> ochPorts = findPorts(intent); + + if (ochPorts == null) { + return Collections.emptyList(); + } + + // Create optical connectivity intent + ConnectPoint srcCP = new ConnectPoint(src.elementId(), ochPorts.getLeft().number()); + ConnectPoint dstCP = new ConnectPoint(dst.elementId(), ochPorts.getRight().number()); + // FIXME: hardcoded ODU signal type + connIntent = OpticalConnectivityIntent.builder() + .appId(appId) + .src(srcCP) + .dst(dstCP) + .signalType(OduSignalType.ODU4) + .bidirectional(intent.isBidirectional()) + .build(); + intentService.submit(connIntent); + } + + // Create optical circuit intent + List<FlowRule> rules = new LinkedList<>(); + rules.add(connectPorts(src, connIntent.getSrc(), intent.priority())); + rules.add(connectPorts(connIntent.getDst(), dst, intent.priority())); + + // Create flow rules for reverse path + if (intent.isBidirectional()) { + rules.add(connectPorts(connIntent.getSrc(), src, intent.priority())); + rules.add(connectPorts(dst, connIntent.getDst(), intent.priority())); + } + + circuitIntent = new FlowRuleIntent(appId, rules, intent.resources()); + + // Save circuit to connectivity intent mapping + deviceResourceService.requestMapping(connIntent.id(), intent.id()); + intents.add(circuitIntent); + + return intents; + } + + /** + * Checks if current allocations on given resource can satisfy request. + * If the resource is null, return true. + * + * @param request the intent making the request + * @param resource the resource on which to map the intent + * @return true if the resource can accept the request, false otherwise + */ + private boolean isAvailable(Intent request, IntentId resource) { + if (resource == null) { + return true; + } + + Set<IntentId> mapping = deviceResourceService.getMapping(resource); + + if (mapping == null) { + return true; + } + + return mapping.size() < maxCapacity; + } + + private boolean isAllowed(OpticalCircuitIntent circuitIntent, OpticalConnectivityIntent connIntent) { + ConnectPoint srcStaticPort = staticPort(circuitIntent.getSrc()); + if (srcStaticPort != null) { + if (!srcStaticPort.equals(connIntent.getSrc())) { + return false; + } + } + + ConnectPoint dstStaticPort = staticPort(circuitIntent.getDst()); + if (dstStaticPort != null) { + if (!dstStaticPort.equals(connIntent.getDst())) { + return false; + } + } + + return true; + } + + /** + * Returns existing and available optical connectivity intent that matches the given circuit intent. + * + * @param circuitIntent optical circuit intent + * @return existing optical connectivity intent, null otherwise. + */ + private OpticalConnectivityIntent findOpticalConnectivityIntent(OpticalCircuitIntent circuitIntent) { + for (Intent intent : intentService.getIntents()) { + if (!(intent instanceof OpticalConnectivityIntent)) { + continue; + } + + OpticalConnectivityIntent connIntent = (OpticalConnectivityIntent) intent; + + ConnectPoint src = circuitIntent.getSrc(); + ConnectPoint dst = circuitIntent.getDst(); + // Ignore if the intents don't have identical src and dst devices + if (!src.deviceId().equals(connIntent.getSrc().deviceId()) && + !dst.deviceId().equals(connIntent.getDst().deviceId())) { + continue; + } + + if (!isAllowed(circuitIntent, connIntent)) { + continue; + } + + if (isAvailable(circuitIntent, connIntent.id())) { + return connIntent; + } + } + + return null; + } + + private ConnectPoint staticPort(ConnectPoint connectPoint) { + Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port()); + + String staticPort = port.annotations().value(AnnotationKeys.STATIC_PORT); + + // FIXME: need a better way to match the port + if (staticPort != null) { + for (Port p : deviceService.getPorts(connectPoint.deviceId())) { + if (staticPort.equals(p.number().name())) { + return new ConnectPoint(p.element().id(), p.number()); + } + } + } + + return null; + } + + private OchPort findAvailableOchPort(ConnectPoint oduPort, OpticalCircuitIntent circuitIntent) { + // First see if the port mappings are constrained + ConnectPoint ochCP = staticPort(oduPort); + + if (ochCP != null) { + OchPort ochPort = (OchPort) deviceService.getPort(ochCP.deviceId(), ochCP.port()); + IntentId intentId = deviceResourceService.getAllocations(ochPort); + if (isAvailable(circuitIntent, intentId)) { + return ochPort; + } + } + + // No port constraints, so find any port that works + List<Port> ports = deviceService.getPorts(oduPort.deviceId()); + + for (Port port : ports) { + if (!(port instanceof OchPort)) { + continue; + } + + IntentId intentId = deviceResourceService.getAllocations(port); + if (isAvailable(circuitIntent, intentId)) { + return (OchPort) port; + } + } + + return null; + } + + private Pair<OchPort, OchPort> findPorts(OpticalCircuitIntent intent) { + + OchPort srcPort = findAvailableOchPort(intent.getSrc(), intent); + if (srcPort == null) { + return null; + } + + OchPort dstPort = findAvailableOchPort(intent.getDst(), intent); + if (dstPort == null) { + return null; + } + + return Pair.of(srcPort, dstPort); + } + + /** + * Builds flow rule for mapping between two ports. + * + * @param src source port + * @param dst destination port + * @return flow rules + */ + private FlowRule connectPorts(ConnectPoint src, ConnectPoint dst, int priority) { + checkArgument(src.deviceId().equals(dst.deviceId())); + + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); + + selectorBuilder.matchInPort(src.port()); + //selectorBuilder.add(Criteria.matchCltSignalType) + treatmentBuilder.setOutput(dst.port()); + //treatmentBuilder.add(Instructions.modL1OduSignalType) + + FlowRule flowRule = DefaultFlowRule.builder() + .forDevice(src.deviceId()) + .withSelector(selectorBuilder.build()) + .withTreatment(treatmentBuilder.build()) + .withPriority(priority) + .fromApp(appId) + .makePermanent() + .build(); + + return flowRule; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java new file mode 100644 index 00000000..c60325a7 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java @@ -0,0 +1,305 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.util.Frequency; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.OchPort; +import org.onosproject.net.OchSignal; +import org.onosproject.net.OchSignalType; +import org.onosproject.net.OmsPort; +import org.onosproject.net.Path; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.OpticalConnectivityIntent; +import org.onosproject.net.intent.OpticalPathIntent; +import org.onosproject.net.intent.impl.IntentCompilationException; +import org.onosproject.net.resource.ResourceAllocation; +import org.onosproject.net.resource.ResourceType; +import org.onosproject.net.resource.device.DeviceResourceService; +import org.onosproject.net.resource.link.DefaultLinkResourceRequest; +import org.onosproject.net.resource.link.LambdaResource; +import org.onosproject.net.resource.link.LambdaResourceAllocation; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.resource.link.LinkResourceRequest; +import org.onosproject.net.resource.link.LinkResourceService; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyEdge; +import org.onosproject.net.topology.TopologyService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * An intent compiler for {@link org.onosproject.net.intent.OpticalConnectivityIntent}. + */ +@Component(immediate = true) +public class OpticalConnectivityIntentCompiler implements IntentCompiler<OpticalConnectivityIntent> { + + protected static final Logger log = LoggerFactory.getLogger(OpticalConnectivityIntentCompiler.class); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkResourceService linkResourceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceResourceService deviceResourceService; + + @Activate + public void activate() { + intentManager.registerCompiler(OpticalConnectivityIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(OpticalConnectivityIntent.class); + } + + @Override + public List<Intent> compile(OpticalConnectivityIntent intent, + List<Intent> installable, + Set<LinkResourceAllocations> resources) { + // Check if source and destination are optical OCh ports + ConnectPoint src = intent.getSrc(); + ConnectPoint dst = intent.getDst(); + Port srcPort = deviceService.getPort(src.deviceId(), src.port()); + Port dstPort = deviceService.getPort(dst.deviceId(), dst.port()); + checkArgument(srcPort instanceof OchPort); + checkArgument(dstPort instanceof OchPort); + + log.debug("Compiling optical connectivity intent between {} and {}", src, dst); + + // Reserve OCh ports + if (!deviceResourceService.requestPorts(ImmutableSet.of(srcPort, dstPort), intent)) { + throw new IntentCompilationException("Unable to reserve ports for intent " + intent); + } + + // Calculate available light paths + Set<Path> paths = getOpticalPaths(intent); + + // Use first path that can be successfully reserved + for (Path path : paths) { + + // Static or dynamic lambda allocation + String staticLambda = srcPort.annotations().value(AnnotationKeys.STATIC_LAMBDA); + OchPort srcOchPort = (OchPort) srcPort; + OchPort dstOchPort = (OchPort) dstPort; + OchSignal ochSignal; + + // FIXME: need to actually reserve the lambda for static lambda's + if (staticLambda != null) { + ochSignal = new OchSignal(Frequency.ofHz(Long.parseLong(staticLambda)), + srcOchPort.lambda().channelSpacing(), + srcOchPort.lambda().slotGranularity()); + } else if (!srcOchPort.isTunable() || !dstOchPort.isTunable()) { + // FIXME: also check OCh port + ochSignal = srcOchPort.lambda(); + } else { + // Request and reserve lambda on path + LinkResourceAllocations linkAllocs = assignWavelength(intent, path); + if (linkAllocs == null) { + continue; + } + LambdaResourceAllocation lambdaAlloc = getWavelength(path, linkAllocs); + OmsPort omsPort = (OmsPort) deviceService.getPort(path.src().deviceId(), path.src().port()); + ochSignal = new OchSignal(lambdaAlloc.lambda().toInt(), omsPort.maxFrequency(), omsPort.grid()); + } + + // Create installable optical path intent + // Only support fixed grid for now + OchSignalType signalType = OchSignalType.FIXED_GRID; + + Intent newIntent = OpticalPathIntent.builder() + .appId(intent.appId()) + .src(intent.getSrc()) + .dst(intent.getDst()) + .path(path) + .lambda(ochSignal) + .signalType(signalType) + .bidirectional(intent.isBidirectional()) + .build(); + + return ImmutableList.of(newIntent); + } + + // Release port allocations if unsuccessful + deviceResourceService.releasePorts(intent.id()); + + throw new IntentCompilationException("Unable to find suitable lightpath for intent " + intent); + } + + /** + * Find the lambda allocated to the path. + * + * @param path the path + * @param linkAllocs the link allocations + * @return lambda allocated to the given path + */ + private LambdaResourceAllocation getWavelength(Path path, LinkResourceAllocations linkAllocs) { + for (Link link : path.links()) { + for (ResourceAllocation alloc : linkAllocs.getResourceAllocation(link)) { + if (alloc.type() == ResourceType.LAMBDA) { + return (LambdaResourceAllocation) alloc; + } + } + } + + return null; + } + + /** + * Request and reserve first available wavelength across path. + * + * @param path path in WDM topology + * @return first available lambda resource allocation + */ + private LinkResourceAllocations assignWavelength(Intent intent, Path path) { + LinkResourceRequest.Builder request = + DefaultLinkResourceRequest.builder(intent.id(), path.links()) + .addLambdaRequest(); + + LinkResourceAllocations allocations = linkResourceService.requestResources(request.build()); + + if (!checkWavelengthContinuity(allocations, path)) { + linkResourceService.releaseResources(allocations); + return null; + } + + return allocations; + } + + /** + * Checks wavelength continuity constraint across path, i.e., an identical lambda is used on all links. + * @return true if wavelength continuity is met, false otherwise + */ + private boolean checkWavelengthContinuity(LinkResourceAllocations allocations, Path path) { + if (allocations == null) { + return false; + } + + LambdaResource lambda = null; + + for (Link link : path.links()) { + for (ResourceAllocation alloc : allocations.getResourceAllocation(link)) { + if (alloc.type() == ResourceType.LAMBDA) { + LambdaResource nextLambda = ((LambdaResourceAllocation) alloc).lambda(); + if (nextLambda == null) { + return false; + } + if (lambda == null) { + lambda = nextLambda; + continue; + } + if (!lambda.equals(nextLambda)) { + return false; + } + } + } + } + + return true; + } + + private ConnectPoint staticPort(ConnectPoint connectPoint) { + Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port()); + + String staticPort = port.annotations().value(AnnotationKeys.STATIC_PORT); + + // FIXME: need a better way to match the port + if (staticPort != null) { + for (Port p : deviceService.getPorts(connectPoint.deviceId())) { + if (staticPort.equals(p.number().name())) { + return new ConnectPoint(p.element().id(), p.number()); + } + } + } + + return null; + } + + /** + * Calculates optical paths in WDM topology. + * + * @param intent optical connectivity intent + * @return set of paths in WDM topology + */ + private Set<Path> getOpticalPaths(OpticalConnectivityIntent intent) { + // Route in WDM topology + Topology topology = topologyService.currentTopology(); + LinkWeight weight = new LinkWeight() { + @Override + public double weight(TopologyEdge edge) { + // Disregard inactive or non-optical links + if (edge.link().state() == Link.State.INACTIVE) { + return -1; + } + if (edge.link().type() != Link.Type.OPTICAL) { + return -1; + } + // Adhere to static port mappings + DeviceId srcDeviceId = edge.link().src().deviceId(); + if (srcDeviceId.equals(intent.getSrc().deviceId())) { + ConnectPoint srcStaticPort = staticPort(intent.getSrc()); + if (srcStaticPort != null) { + return srcStaticPort.equals(edge.link().src()) ? 1 : -1; + } + } + DeviceId dstDeviceId = edge.link().dst().deviceId(); + if (dstDeviceId.equals(intent.getDst().deviceId())) { + ConnectPoint dstStaticPort = staticPort(intent.getDst()); + if (dstStaticPort != null) { + return dstStaticPort.equals(edge.link().dst()) ? 1 : -1; + } + } + + return 1; + } + }; + + ConnectPoint start = intent.getSrc(); + ConnectPoint end = intent.getDst(); + Set<Path> paths = topologyService.getPaths(topology, start.deviceId(), + end.deviceId(), weight); + + return paths; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompiler.java new file mode 100644 index 00000000..ca9ae5cc --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompiler.java @@ -0,0 +1,195 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import com.google.common.collect.Lists; +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.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.OpticalPathIntent; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.resource.link.LinkResourceService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +@Component(immediate = true) +public class OpticalPathIntentCompiler implements IntentCompiler<OpticalPathIntent> { + + private static final Logger log = LoggerFactory.getLogger(OpticalPathIntentCompiler.class); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkResourceService resourceService; + + private ApplicationId appId; + + @Activate + public void activate() { + appId = coreService.registerApplication("org.onosproject.net.intent"); + intentManager.registerCompiler(OpticalPathIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(OpticalPathIntent.class); + } + + @Override + public List<Intent> compile(OpticalPathIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + log.debug("Compiling optical path intent between {} and {}", intent.src(), intent.dst()); + + // Create rules for forward and reverse path + List<FlowRule> rules = createRules(intent); + if (intent.isBidirectional()) { + rules.addAll(createReverseRules(intent)); + } + + return Collections.singletonList(new FlowRuleIntent(appId, rules, intent.resources())); + } + + /** + * Create rules for the forward path of the intent. + * + * @param intent the intent + * @return list of flow rules + */ + private List<FlowRule> createRules(OpticalPathIntent intent) { + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + selectorBuilder.matchInPort(intent.src().port()); + + List<FlowRule> rules = new LinkedList<>(); + ConnectPoint current = intent.src(); + + for (Link link : intent.path().links()) { + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); + treatmentBuilder.add(Instructions.modL0Lambda(intent.lambda())); + treatmentBuilder.setOutput(link.src().port()); + + FlowRule rule = DefaultFlowRule.builder() + .forDevice(current.deviceId()) + .withSelector(selectorBuilder.build()) + .withTreatment(treatmentBuilder.build()) + .withPriority(intent.priority()) + .fromApp(appId) + .makePermanent() + .build(); + + rules.add(rule); + + current = link.dst(); + selectorBuilder.matchInPort(link.dst().port()); + selectorBuilder.add(Criteria.matchLambda(intent.lambda())); + selectorBuilder.add(Criteria.matchOchSignalType(intent.signalType())); + } + + // Build the egress ROADM rule + TrafficTreatment.Builder treatmentLast = DefaultTrafficTreatment.builder(); + treatmentLast.setOutput(intent.dst().port()); + + FlowRule rule = new DefaultFlowRule.Builder() + .forDevice(intent.dst().deviceId()) + .withSelector(selectorBuilder.build()) + .withTreatment(treatmentLast.build()) + .withPriority(intent.priority()) + .fromApp(appId) + .makePermanent() + .build(); + rules.add(rule); + + return rules; + } + + /** + * Create rules for the reverse path of the intent. + * + * @param intent the intent + * @return list of flow rules + */ + private List<FlowRule> createReverseRules(OpticalPathIntent intent) { + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + selectorBuilder.matchInPort(intent.dst().port()); + + List<FlowRule> rules = new LinkedList<>(); + ConnectPoint current = intent.dst(); + + for (Link link : Lists.reverse(intent.path().links())) { + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); + treatmentBuilder.add(Instructions.modL0Lambda(intent.lambda())); + treatmentBuilder.setOutput(link.dst().port()); + + FlowRule rule = DefaultFlowRule.builder() + .forDevice(current.deviceId()) + .withSelector(selectorBuilder.build()) + .withTreatment(treatmentBuilder.build()) + .withPriority(intent.priority()) + .fromApp(appId) + .makePermanent() + .build(); + + rules.add(rule); + + current = link.src(); + selectorBuilder.matchInPort(link.src().port()); + selectorBuilder.add(Criteria.matchLambda(intent.lambda())); + selectorBuilder.add(Criteria.matchOchSignalType(intent.signalType())); + } + + // Build the egress ROADM rule + TrafficTreatment.Builder treatmentLast = DefaultTrafficTreatment.builder(); + treatmentLast.setOutput(intent.src().port()); + + FlowRule rule = new DefaultFlowRule.Builder() + .forDevice(intent.src().deviceId()) + .withSelector(selectorBuilder.build()) + .withTreatment(treatmentLast.build()) + .withPriority(intent.priority()) + .fromApp(appId) + .makePermanent() + .build(); + rules.add(rule); + + return rules; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java new file mode 100644 index 00000000..7add2173 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +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.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.PathIntent; +import org.onosproject.net.resource.link.LinkResourceAllocations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@Component(immediate = true) +public class PathIntentCompiler implements IntentCompiler<PathIntent> { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentExtensionService intentManager; + + private ApplicationId appId; + + @Activate + public void activate() { + appId = coreService.registerApplication("org.onosproject.net.intent"); + intentManager.registerCompiler(PathIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(PathIntent.class); + } + + @Override + public List<Intent> compile(PathIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + // Note: right now recompile is not considered + // TODO: implement recompile behavior + + List<Link> links = intent.path().links(); + List<FlowRule> rules = new ArrayList<>(links.size() - 1); + + for (int i = 0; i < links.size() - 1; i++) { + ConnectPoint ingress = links.get(i).dst(); + ConnectPoint egress = links.get(i + 1).src(); + FlowRule rule = createFlowRule(intent.selector(), intent.treatment(), + ingress, egress, intent.priority(), + isLast(links, i)); + rules.add(rule); + } + + return Collections.singletonList(new FlowRuleIntent(appId, null, rules, intent.resources())); + } + + private FlowRule createFlowRule(TrafficSelector originalSelector, TrafficTreatment originalTreatment, + ConnectPoint ingress, ConnectPoint egress, + int priority, boolean last) { + TrafficSelector selector = DefaultTrafficSelector.builder(originalSelector) + .matchInPort(ingress.port()) + .build(); + + TrafficTreatment.Builder treatmentBuilder; + if (last) { + treatmentBuilder = DefaultTrafficTreatment.builder(originalTreatment); + } else { + treatmentBuilder = DefaultTrafficTreatment.builder(); + } + TrafficTreatment treatment = treatmentBuilder.setOutput(egress.port()).build(); + + return DefaultFlowRule.builder() + .forDevice(ingress.deviceId()) + .withSelector(selector) + .withTreatment(treatment) + .withPriority(priority) + .fromApp(appId) + .makePermanent() + .build(); + } + + private boolean isLast(List<Link> links, int i) { + return i == links.size() - 2; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java new file mode 100644 index 00000000..5644ee22 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.PathIntent; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.resource.link.LinkResourceAllocations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; + +/** + * An intent compiler for {@link org.onosproject.net.intent.PointToPointIntent}. + */ +@Component(immediate = true) +public class PointToPointIntentCompiler + extends ConnectivityIntentCompiler<PointToPointIntent> { + + // TODO: use off-the-shell core provider ID + private static final ProviderId PID = + new ProviderId("core", "org.onosproject.core", true); + // TODO: consider whether the default cost is appropriate or not + public static final int DEFAULT_COST = 1; + + @Activate + public void activate() { + intentManager.registerCompiler(PointToPointIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(PointToPointIntent.class); + } + + @Override + public List<Intent> compile(PointToPointIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + + ConnectPoint ingressPoint = intent.ingressPoint(); + ConnectPoint egressPoint = intent.egressPoint(); + + if (ingressPoint.deviceId().equals(egressPoint.deviceId())) { + List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false)); + return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST), intent)); + } + + List<Link> links = new ArrayList<>(); + Path path = getPath(intent, ingressPoint.deviceId(), + egressPoint.deviceId()); + + links.add(createEdgeLink(ingressPoint, true)); + links.addAll(path.links()); + links.add(createEdgeLink(egressPoint, false)); + + return asList(createPathIntent(new DefaultPath(PID, links, path.cost(), + path.annotations()), intent)); + } + + /** + * Creates a path intent from the specified path and original + * connectivity intent. + * + * @param path path to create an intent for + * @param intent original intent + */ + private Intent createPathIntent(Path path, + PointToPointIntent intent) { + return PathIntent.builder() + .appId(intent.appId()) + .selector(intent.selector()) + .treatment(intent.treatment()) + .path(path) + .constraints(intent.constraints()) + .priority(intent.priority()) + .build(); + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java new file mode 100644 index 00000000..56565908 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java @@ -0,0 +1,85 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.LinkCollectionIntent; +import org.onosproject.net.intent.SinglePointToMultiPointIntent; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.resource.link.LinkResourceAllocations; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Component(immediate = true) +public class SinglePointToMultiPointIntentCompiler + extends ConnectivityIntentCompiler<SinglePointToMultiPointIntent> { + + // TODO: use off-the-shell core provider ID + private static final ProviderId PID = + new ProviderId("core", "org.onosproject.core", true); + + @Activate + public void activate() { + intentManager.registerCompiler(SinglePointToMultiPointIntent.class, + this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(SinglePointToMultiPointIntent.class); + } + + + @Override + public List<Intent> compile(SinglePointToMultiPointIntent intent, + List<Intent> installable, + Set<LinkResourceAllocations> resources) { + Set<Link> links = new HashSet<>(); + + for (ConnectPoint egressPoint : intent.egressPoints()) { + if (egressPoint.deviceId().equals(intent.ingressPoint().deviceId())) { + continue; + } + + Path path = getPath(intent, intent.ingressPoint().deviceId(), egressPoint.deviceId()); + links.addAll(path.links()); + } + + Intent result = LinkCollectionIntent.builder() + .appId(intent.appId()) + .key(intent.key()) + .selector(intent.selector()) + .treatment(intent.treatment()) + .links(links) + .ingressPoints(ImmutableSet.of(intent.ingressPoint())) + .egressPoints(intent.egressPoints()) + .priority(intent.priority()) + .constraints(intent.constraints()) + .build(); + + return Collections.singletonList(result); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/TwoWayP2PIntentCompiler.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/TwoWayP2PIntentCompiler.java new file mode 100644 index 00000000..50a67546 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/TwoWayP2PIntentCompiler.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import com.google.common.collect.Lists; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.intent.TwoWayP2PIntent; +import org.onosproject.net.resource.link.LinkResourceAllocations; + +import java.util.List; +import java.util.Set; + +/** + * A intent compiler for {@link org.onosproject.net.intent.TwoWayP2PIntent}. + */ +@Component(immediate = true) +public class TwoWayP2PIntentCompiler + extends ConnectivityIntentCompiler<TwoWayP2PIntent> { + + @Activate + public void activate() { + intentManager.registerCompiler(TwoWayP2PIntent.class, this); + } + + @Deactivate + public void deactivate() { + intentManager.unregisterCompiler(TwoWayP2PIntent.class); + } + + @Override + public List<Intent> compile(TwoWayP2PIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + return Lists.newArrayList( + PointToPointIntent.builder() + .appId(intent.appId()) + .key(intent.key()) + .selector(intent.selector()) + .treatment(intent.treatment()) + .ingressPoint(intent.one()) + .egressPoint(intent.two()) + .constraints(intent.constraints()) + .priority(intent.priority()) + .build(), + PointToPointIntent.builder() + .appId(intent.appId()) + .key(intent.key()) + .selector(intent.selector()) + .treatment(intent.treatment()) + .ingressPoint(intent.two()) + .egressPoint(intent.one()) + .constraints(intent.constraints()) + .priority(intent.priority()) + .build()); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/package-info.java new file mode 100644 index 00000000..beaf5ed0 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementations of builtin intent compilers. + */ +package org.onosproject.net.intent.impl.compiler;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/package-info.java new file mode 100644 index 00000000..8c516c64 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for tracking high-level intents for treatment of selected + * network traffic. + */ +package org.onosproject.net.intent.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Compiling.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Compiling.java new file mode 100644 index 00000000..5078b5dc --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Compiling.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentException; +import org.onosproject.net.intent.impl.IntentProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Represents a phase where an intent is being compiled or recompiled. + */ +class Compiling implements IntentProcessPhase { + + private static final Logger log = LoggerFactory.getLogger(Compiling.class); + + private final IntentProcessor processor; + private final IntentData data; + private final Optional<IntentData> stored; + + /** + * Creates a intent recompiling phase. + * + * @param processor intent processor that does work for recompiling + * @param data intent data containing an intent to be recompiled + * @param stored intent data stored in the store + */ + Compiling(IntentProcessor processor, IntentData data, Optional<IntentData> stored) { + this.processor = checkNotNull(processor); + this.data = checkNotNull(data); + this.stored = checkNotNull(stored); + } + + @Override + public Optional<IntentProcessPhase> execute() { + try { + List<Intent> compiled = processor.compile(data.intent(), + //TODO consider passing an optional here in the future + stored.isPresent() ? stored.get().installables() : null); + data.setInstallables(compiled); + return Optional.of(new Installing(processor, data, stored)); + } catch (IntentException e) { + log.debug("Unable to compile intent {} due to: {}", data.intent(), e); + if (stored.isPresent() && !stored.get().installables().isEmpty()) { + // removing orphaned flows and deallocating resources + data.setInstallables(stored.get().installables()); + return Optional.of(new Withdrawing(processor, data)); + } else { + return Optional.of(new Failed(data)); + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Corrupt.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Corrupt.java new file mode 100644 index 00000000..2fbe1641 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Corrupt.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.IntentState.CORRUPT; + +/** + * A class representing errors removing or installing intents. + */ +public class Corrupt extends FinalIntentProcessPhase { + + private final IntentData intentData; + + /** + * Create an instance with the specified data. + * + * @param intentData intentData + */ + Corrupt(IntentData intentData) { + this.intentData = checkNotNull(intentData); + this.intentData.setState(CORRUPT); + } + + @Override + public IntentData data() { + return intentData; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Failed.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Failed.java new file mode 100644 index 00000000..7f628e3c --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Failed.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.IntentState.FAILED; + +/** + * Represents a phase where the compile has failed. + */ +public class Failed extends FinalIntentProcessPhase { + + private final IntentData intentData; + + /** + * Create an instance with the specified data. + * + * @param intentData intentData + */ + Failed(IntentData intentData) { + this.intentData = checkNotNull(intentData); + this.intentData.setState(FAILED); + } + + @Override + public IntentData data() { + return intentData; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/FinalIntentProcessPhase.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/FinalIntentProcessPhase.java new file mode 100644 index 00000000..c67b93b5 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/FinalIntentProcessPhase.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; + +import java.util.Optional; + +/** + * Represents a final phase of processing an intent. + */ +public abstract class FinalIntentProcessPhase implements IntentProcessPhase { + + @Override + public final Optional<IntentProcessPhase> execute() { + preExecute(); + return Optional.empty(); + } + + /** + * Executes operations that must take place before the phase starts. + */ + protected void preExecute() {} + + /** + * Returns the IntentData object being acted on by this phase. + * + * @return intent data object for the phase + */ + public abstract IntentData data(); +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java new file mode 100644 index 00000000..a75d7cc8 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.impl.IntentProcessor; + +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.transferErrorCount; + +/** + * Represents a phase where intent installation has been requested. + */ +final class InstallRequest implements IntentProcessPhase { + + private final IntentProcessor processor; + private final IntentData data; + private final Optional<IntentData> stored; + + /** + * Creates an install request phase. + * + * @param processor intent processor to be passed to intent process phases + * generated after this phase + * @param intentData intent data to be processed + * @param stored intent data stored in the store + */ + InstallRequest(IntentProcessor processor, IntentData intentData, Optional<IntentData> stored) { + this.processor = checkNotNull(processor); + this.data = checkNotNull(intentData); + this.stored = checkNotNull(stored); + } + + @Override + public Optional<IntentProcessPhase> execute() { + transferErrorCount(data, stored); + + return Optional.of(new Compiling(processor, data, stored)); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Installing.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Installing.java new file mode 100644 index 00000000..2ff7ca8b --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Installing.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.impl.IntentProcessor; + +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.IntentState.INSTALLING; + +/** + * Represents a phase where an intent is being installed. + */ +class Installing extends FinalIntentProcessPhase { + + private final IntentProcessor processor; + private final IntentData data; + private final Optional<IntentData> stored; + + /** + * Create an installing phase. + * + * @param processor intent processor that does work for installing + * @param data intent data containing an intent to be installed + * @param stored intent data already stored + */ + Installing(IntentProcessor processor, IntentData data, Optional<IntentData> stored) { + this.processor = checkNotNull(processor); + this.data = checkNotNull(data); + this.stored = checkNotNull(stored); + this.data.setState(INSTALLING); + } + + @Override + public void preExecute() { + processor.apply(stored, Optional.of(data)); + } + + @Override + public IntentData data() { + return data; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java new file mode 100644 index 00000000..bce572c6 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.impl.IntentProcessor; + +import java.util.Objects; +import java.util.Optional; + +/** + * Represents a phase of processing an intent. + */ +public interface IntentProcessPhase { + + /** + * Execute the procedure represented by the instance + * and generates the next update instance. + * + * @return next update + */ + Optional<IntentProcessPhase> execute(); + + /** + * Create a starting intent process phase according to intent data this class holds. + * + * @param processor intent processor to be passed to intent process phases + * generated while this instance is working + * @param data intent data to be processed + * @param current intent date that is stored in the store + * @return starting intent process phase + */ + static IntentProcessPhase newInitialPhase(IntentProcessor processor, + IntentData data, IntentData current) { + switch (data.request()) { + case INSTALL_REQ: + return new InstallRequest(processor, data, Optional.ofNullable(current)); + case WITHDRAW_REQ: + return new WithdrawRequest(processor, data, Optional.ofNullable(current)); + case PURGE_REQ: + return new PurgeRequest(data, Optional.ofNullable(current)); + default: + // illegal state + return new Failed(data); + } + } + + static void transferErrorCount(IntentData data, Optional<IntentData> stored) { + if (stored.isPresent()) { + IntentData storedData = stored.get(); + if (Objects.equals(data.intent(), storedData.intent()) && + Objects.equals(data.request(), storedData.request())) { + data.setErrorCount(storedData.errorCount()); + } else { + data.setErrorCount(0); + } + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentWorker.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentWorker.java new file mode 100644 index 00000000..9ddcf40e --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentWorker.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + + +import java.util.Optional; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Worker to process a submitted intent. {@link #call()} method generates + */ +public final class IntentWorker implements Callable<FinalIntentProcessPhase> { + + private final IntentProcessPhase initial; + + /** + * Create an instance with the specified arguments. + * + * @param initial initial intent process phase + */ + public IntentWorker(IntentProcessPhase initial) { + this.initial = checkNotNull(initial); + } + + @Override + public FinalIntentProcessPhase call() throws Exception { + IntentProcessPhase update = initial; + Optional<IntentProcessPhase> currentPhase = Optional.of(update); + IntentProcessPhase previousPhase = update; + + while (currentPhase.isPresent()) { + previousPhase = currentPhase.get(); + currentPhase = previousPhase.execute(); + } + return (FinalIntentProcessPhase) previousPhase; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/PurgeRequest.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/PurgeRequest.java new file mode 100644 index 00000000..69126dfb --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/PurgeRequest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentState; +import org.slf4j.Logger; + +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Represents a phase of requesting a purge of an intent. + * <p> + * Note: The purge will only succeed if the intent is FAILED or WITHDRAWN. + * </p> + */ +final class PurgeRequest extends FinalIntentProcessPhase { + + private static final Logger log = getLogger(PurgeRequest.class); + + private final IntentData data; + private final Optional<IntentData> stored; + + PurgeRequest(IntentData intentData, Optional<IntentData> stored) { + this.data = checkNotNull(intentData); + this.stored = checkNotNull(stored); + } + + private boolean shouldAcceptPurge() { + if (!stored.isPresent()) { + log.info("Purge for intent {}, but intent is not present", + data.key()); + return true; + } + + IntentData storedData = stored.get(); + if (storedData.state() == IntentState.WITHDRAWN + || storedData.state() == IntentState.FAILED) { + return true; + } + log.info("Purge for intent {} is rejected because intent state is {}", + data.key(), storedData.state()); + return false; + } + + @Override + public IntentData data() { + if (shouldAcceptPurge()) { + return data; + } else { + return stored.get(); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java new file mode 100644 index 00000000..8a0709e6 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.impl.IntentProcessor; + +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.transferErrorCount; + +/** + * Represents a phase of requesting a withdraw of an intent. + */ +final class WithdrawRequest implements IntentProcessPhase { + + private final IntentProcessor processor; + private final IntentData data; + private final Optional<IntentData> stored; + + /** + * Creates a withdraw request phase. + * + * @param processor intent processor to be passed to intent process phases + * generated after this phase + * @param intentData intent data to be processed + * @param stored intent data stored in the store + */ + WithdrawRequest(IntentProcessor processor, IntentData intentData, Optional<IntentData> stored) { + this.processor = checkNotNull(processor); + this.data = checkNotNull(intentData); + this.stored = checkNotNull(stored); + } + + @Override + public Optional<IntentProcessPhase> execute() { + //TODO perhaps we want to validate that the pending and current are the + // same version i.e. they are the same + // Note: this call is not just the symmetric version of submit + + transferErrorCount(data, stored); + + if (!stored.isPresent() || stored.get().installables().isEmpty()) { + switch (data.request()) { + case INSTALL_REQ: + return Optional.of(new Failed(data)); + case WITHDRAW_REQ: + default: //TODO "default" case should not happen + return Optional.of(new Withdrawn(data)); + } + } + + data.setInstallables(stored.get().installables()); + return Optional.of(new Withdrawing(processor, data)); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawing.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawing.java new file mode 100644 index 00000000..29bc4711 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawing.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.impl.IntentProcessor; + +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.IntentState.WITHDRAWING; + +/** + * Represents a phase where an intent is withdrawing. + */ +class Withdrawing extends FinalIntentProcessPhase { + + private final IntentProcessor processor; + private final IntentData data; + + /** + * Creates a withdrawing phase. + * + * @param processor intent processor that does work for withdrawing + * @param data intent data containing an intent to be withdrawn + */ + Withdrawing(IntentProcessor processor, IntentData data) { + this.processor = checkNotNull(processor); + this.data = checkNotNull(data); + this.data.setState(WITHDRAWING); + } + + @Override + protected void preExecute() { + processor.apply(Optional.of(data), Optional.empty()); + } + + @Override + public IntentData data() { + return data; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawn.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawn.java new file mode 100644 index 00000000..264f74c6 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawn.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.onosproject.net.intent.IntentData; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.IntentState.WITHDRAWN; + +/** + * Represents a phase where an intent has been withdrawn. + */ +final class Withdrawn extends FinalIntentProcessPhase { + + private final IntentData data; + + /** + * Create a withdrawn phase. + * + * @param data intent data containing an intent to be withdrawn + */ + Withdrawn(IntentData data) { + this.data = checkNotNull(data); + this.data.setState(WITHDRAWN); + } + + @Override + public IntentData data() { + return data; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/package-info.java new file mode 100644 index 00000000..56e54308 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementations of various intent processing phases. + */ +package org.onosproject.net.intent.impl.phase;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java new file mode 100644 index 00000000..a6b08f62 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014-2015 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.net.link.impl; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.time.Duration; + +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.config.ConfigOperator; +import org.onosproject.net.config.basics.BasicLinkConfig; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Link; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; +import org.slf4j.Logger; + +/** + * Implementations of merge policies for various sources of link configuration + * information. This includes applications, provides, and network configurations. + */ +public final class BasicLinkOperator implements ConfigOperator { + + private static final long DEF_BANDWIDTH = -1L; + private static final Duration DEF_DURATION = Duration.ofNanos(-1L); + private static final Logger log = getLogger(BasicLinkOperator.class); + + private BasicLinkOperator() { + } + + /** + * Generates a LinkDescription containing fields from a LinkDescription and + * a LinkConfig. + * + * @param cfg the link config entity from network config + * @param descr a LinkDescription + * @return LinkDescription based on both sources + */ + public static LinkDescription combine(BasicLinkConfig cfg, LinkDescription descr) { + if (cfg == null) { + return descr; + } + + // cfg.type() defaults to DIRECT, so there is a risk of unwanted override. + // do we want this behavior? + Link.Type type = descr.type(); + if (cfg.type() != type) { + type = cfg.type(); + } + + SparseAnnotations sa = combine(cfg, descr.annotations()); + return new DefaultLinkDescription(descr.src(), descr.dst(), type, sa); + } + + /** + * Generates an annotation from an existing annotation and LinkConfig. + * + * @param cfg the link config entity from network config + * @param an the annotation + * @return annotation combining both sources + */ + public static SparseAnnotations combine(BasicLinkConfig cfg, SparseAnnotations an) { + DefaultAnnotations.Builder b = DefaultAnnotations.builder(); + if (cfg.latency() != DEF_DURATION) { + b.set(AnnotationKeys.LATENCY, cfg.latency().toString()); + } + if (cfg.bandwidth() != DEF_BANDWIDTH) { + b.set(AnnotationKeys.BANDWIDTH, String.valueOf(cfg.bandwidth())); + } + return DefaultAnnotations.union(an, b.build()); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java new file mode 100644 index 00000000..157288a4 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java @@ -0,0 +1,343 @@ +/* + * Copyright 2014 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.net.link.impl; + +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.basics.BasicLinkConfig; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Link.State; +import org.onosproject.net.LinkKey; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.link.LinkAdminService; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.link.LinkStore; +import org.onosproject.net.link.LinkStoreDelegate; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.onosproject.net.LinkKey.linkKey; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Provides basic implementation of the link SB & NB APIs. + */ +@Component(immediate = true) +@Service +public class LinkManager + extends AbstractListenerProviderRegistry<LinkEvent, LinkListener, LinkProvider, LinkProviderService> + implements LinkService, LinkAdminService, LinkProviderRegistry { + + private static final String DEVICE_ID_NULL = "Device ID cannot be null"; + private static final String LINK_DESC_NULL = "Link description cannot be null"; + private static final String CONNECT_POINT_NULL = "Connection point cannot be null"; + + private final Logger log = getLogger(getClass()); + + private final LinkStoreDelegate delegate = new InternalStoreDelegate(); + + private final DeviceListener deviceListener = new InternalDeviceListener(); + + private final NetworkConfigListener networkConfigListener = new InternalNetworkConfigListener(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService networkConfigService; + + @Activate + public void activate() { + store.setDelegate(delegate); + eventDispatcher.addSink(LinkEvent.class, listenerRegistry); + deviceService.addListener(deviceListener); + networkConfigService.addListener(networkConfigListener); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + eventDispatcher.removeSink(LinkEvent.class); + deviceService.removeListener(deviceListener); + networkConfigService.removeListener(networkConfigListener); + log.info("Stopped"); + } + + @Override + public int getLinkCount() { + checkPermission(LINK_READ); + return store.getLinkCount(); + } + + @Override + public Iterable<Link> getLinks() { + checkPermission(LINK_READ); + return store.getLinks(); + } + + @Override + public Iterable<Link> getActiveLinks() { + checkPermission(LINK_READ); + return FluentIterable.from(getLinks()) + .filter(new Predicate<Link>() { + + @Override + public boolean apply(Link input) { + return input.state() == State.ACTIVE; + } + }); + } + + @Override + public Set<Link> getDeviceLinks(DeviceId deviceId) { + checkPermission(LINK_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return Sets.union(store.getDeviceEgressLinks(deviceId), + store.getDeviceIngressLinks(deviceId)); + } + + @Override + public Set<Link> getDeviceEgressLinks(DeviceId deviceId) { + checkPermission(LINK_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getDeviceEgressLinks(deviceId); + } + + @Override + public Set<Link> getDeviceIngressLinks(DeviceId deviceId) { + checkPermission(LINK_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getDeviceIngressLinks(deviceId); + } + + @Override + public Set<Link> getLinks(ConnectPoint connectPoint) { + checkPermission(LINK_READ); + checkNotNull(connectPoint, CONNECT_POINT_NULL); + return Sets.union(store.getEgressLinks(connectPoint), + store.getIngressLinks(connectPoint)); + } + + @Override + public Set<Link> getEgressLinks(ConnectPoint connectPoint) { + checkPermission(LINK_READ); + checkNotNull(connectPoint, CONNECT_POINT_NULL); + return store.getEgressLinks(connectPoint); + } + + @Override + public Set<Link> getIngressLinks(ConnectPoint connectPoint) { + checkPermission(LINK_READ); + checkNotNull(connectPoint, CONNECT_POINT_NULL); + return store.getIngressLinks(connectPoint); + } + + @Override + public Link getLink(ConnectPoint src, ConnectPoint dst) { + checkPermission(LINK_READ); + checkNotNull(src, CONNECT_POINT_NULL); + checkNotNull(dst, CONNECT_POINT_NULL); + return store.getLink(src, dst); + } + + @Override + public void removeLinks(ConnectPoint connectPoint) { + if (deviceService.getRole(connectPoint.deviceId()) != MastershipRole.MASTER) { + return; + } + removeLinks(getLinks(connectPoint), false); + } + + @Override + public void removeLinks(DeviceId deviceId) { + if (deviceService.getRole(deviceId) != MastershipRole.MASTER) { + return; + } + removeLinks(getDeviceLinks(deviceId), false); + } + + @Override + public void removeLink(ConnectPoint src, ConnectPoint dst) { + post(store.removeLink(src, dst)); + } + + // Auxiliary interceptor for device remove events to prune links that + // are associated with the removed device or its port. + private class InternalDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + if (event.type() == DeviceEvent.Type.DEVICE_REMOVED) { + removeLinks(event.subject().id()); + } else if (event.type() == DeviceEvent.Type.PORT_REMOVED) { + removeLinks(new ConnectPoint(event.subject().id(), + event.port().number())); + } + } + } + + @Override + protected LinkProviderService createProviderService(LinkProvider provider) { + return new InternalLinkProviderService(provider); + } + + // Personalized link provider service issued to the supplied provider. + private class InternalLinkProviderService + extends AbstractProviderService<LinkProvider> + implements LinkProviderService { + + InternalLinkProviderService(LinkProvider provider) { + super(provider); + } + + @Override + public void linkDetected(LinkDescription linkDescription) { + checkNotNull(linkDescription, LINK_DESC_NULL); + checkValidity(); + linkDescription = validateLink(linkDescription); + LinkEvent event = store.createOrUpdateLink(provider().id(), + linkDescription); + if (event != null) { + log.info("Link {} detected", linkDescription); + post(event); + } + } + + // returns a LinkDescription made from the union of the BasicLinkConfig + // annotations if it exists + private LinkDescription validateLink(LinkDescription linkDescription) { + // TODO Investigate whether this can be made more efficient + BasicLinkConfig cfg = networkConfigService.getConfig(linkKey(linkDescription.src(), + linkDescription.dst()), + BasicLinkConfig.class); + BasicLinkConfig cfgTwo = networkConfigService.getConfig(linkKey(linkDescription.dst(), + linkDescription.src()), + BasicLinkConfig.class); + + checkState(cfg == null || cfg.isAllowed(), "Link " + linkDescription.toString() + " is not allowed"); + checkState(cfgTwo == null || cfgTwo.isAllowed(), "Link " + linkDescription.toString() + " is not allowed"); + + return BasicLinkOperator.combine(cfg, linkDescription); + } + + @Override + public void linkVanished(LinkDescription linkDescription) { + checkNotNull(linkDescription, LINK_DESC_NULL); + checkValidity(); + + ConnectPoint src = linkDescription.src(); + ConnectPoint dst = linkDescription.dst(); + + LinkEvent event = store.removeOrDownLink(src, dst); + if (event != null) { + log.info("Link {} vanished", linkDescription); + post(event); + } + } + + @Override + public void linksVanished(ConnectPoint connectPoint) { + checkNotNull(connectPoint, "Connect point cannot be null"); + checkValidity(); + + log.debug("Links for connection point {} vanished", connectPoint); + // FIXME: This will remove links registered by other providers + removeLinks(getLinks(connectPoint), true); + } + + @Override + public void linksVanished(DeviceId deviceId) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkValidity(); + + log.debug("Links for device {} vanished", deviceId); + removeLinks(getDeviceLinks(deviceId), true); + } + } + + // Removes all links in the specified set and emits appropriate events. + private void removeLinks(Set<Link> links, boolean isSoftRemove) { + for (Link link : links) { + LinkEvent event = isSoftRemove ? + store.removeOrDownLink(link.src(), link.dst()) : + store.removeLink(link.src(), link.dst()); + if (event != null) { + log.info("Link {} removed/vanished", event.subject()); + post(event); + } + } + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements LinkStoreDelegate { + @Override + public void notify(LinkEvent event) { + post(event); + } + } + + // listens for NetworkConfigEvents of type BasicLinkConfig and removes + // links that the config does not allow + private class InternalNetworkConfigListener implements NetworkConfigListener { + @Override + public void event(NetworkConfigEvent event) { + if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED || + event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) && + event.configClass().equals(BasicLinkConfig.class)) { + log.info("Detected Link network config event {}", event.type()); + LinkKey lk = (LinkKey) event.subject(); + BasicLinkConfig cfg = networkConfigService.getConfig(lk, BasicLinkConfig.class); + if (cfg != null && !cfg.isAllowed()) { + log.info("Kicking out links between {} and {}", lk.src(), lk.dst()); + removeLink(lk.src(), lk.dst()); + removeLink(lk.dst(), lk.src()); + } + } + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/package-info.java new file mode 100644 index 00000000..4c32c3fc --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for tracking global inventory of infrastructure links. + */ +package org.onosproject.net.link.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java new file mode 100644 index 00000000..e6d92253 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015 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.net.newresource.impl; + +import org.onosproject.net.Device; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.newresource.ResourceAdminService; +import org.onosproject.net.newresource.ResourcePath; + +import java.util.concurrent.ExecutorService; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An implementation of DeviceListener registering devices as resources. + */ +final class ResourceDeviceListener implements DeviceListener { + + private final ResourceAdminService adminService; + private final ExecutorService executor; + + /** + * Creates an instance with the specified ResourceAdminService and ExecutorService. + * + * @param adminService instance invoked to register resources + * @param executor executor used for processing resource registration + */ + ResourceDeviceListener(ResourceAdminService adminService, ExecutorService executor) { + this.adminService = checkNotNull(adminService); + this.executor = checkNotNull(executor); + } + + @Override + public void event(DeviceEvent event) { + Device device = event.subject(); + switch (event.type()) { + case DEVICE_ADDED: + registerDeviceResource(device); + break; + case DEVICE_REMOVED: + unregisterDeviceResource(device); + break; + case PORT_ADDED: + registerPortResource(device, event.port()); + break; + case PORT_REMOVED: + unregisterPortResource(device, event.port()); + break; + default: + break; + } + } + + private void registerDeviceResource(Device device) { + executor.submit(() -> adminService.registerResources(ResourcePath.ROOT, device.id())); + } + + private void unregisterDeviceResource(Device device) { + executor.submit(() -> adminService.unregisterResources(ResourcePath.ROOT, device.id())); + } + + private void registerPortResource(Device device, Port port) { + ResourcePath parent = new ResourcePath(device.id()); + executor.submit(() -> adminService.registerResources(parent, port.number())); + } + + private void unregisterPortResource(Device device, Port port) { + ResourcePath parent = new ResourcePath(device.id()); + executor.submit(() -> adminService.unregisterResources(parent, port.number())); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceLinkListener.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceLinkListener.java new file mode 100644 index 00000000..f04c78b9 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceLinkListener.java @@ -0,0 +1,152 @@ +/* + * Copyright 2015 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.net.newresource.impl; + +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onlab.util.ItemNotFoundException; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.LinkKey; +import org.onosproject.net.behaviour.MplsQuery; +import org.onosproject.net.behaviour.VlanQuery; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.newresource.ResourceAdminService; +import org.onosproject.net.newresource.ResourcePath; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An implementation of LinkListener registering links as resources. + */ +final class ResourceLinkListener implements LinkListener { + + private static final int TOTAL_VLANS = 1024; + private static final List<VlanId> ENTIRE_VLAN_IDS = getEntireVlans(); + + private static final int TOTAL_MPLS_LABELS = 1048576; + private static final List<MplsLabel> ENTIRE_MPLS_LABELS = getEntireMplsLabels(); + + private final ResourceAdminService adminService; + private final DriverService driverService; + private final ExecutorService executor; + + /** + * Creates an instance with the specified ResourceAdminService and ExecutorService. + * + * @param adminService instance invoked to register resources + * @param driverService driver service instance + * @param executor executor used for processing resource registration + */ + ResourceLinkListener(ResourceAdminService adminService, DriverService driverService, ExecutorService executor) { + this.adminService = checkNotNull(adminService); + this.driverService = checkNotNull(driverService); + this.executor = checkNotNull(executor); + } + + @Override + public void event(LinkEvent event) { + Link link = event.subject(); + switch (event.type()) { + case LINK_ADDED: + registerLinkResource(link); + break; + case LINK_REMOVED: + unregisterLinkResource(link); + break; + default: + break; + } + } + + private void registerLinkResource(Link link) { + executor.submit(() -> { + // register the link + LinkKey linkKey = LinkKey.linkKey(link); + adminService.registerResources(ResourcePath.ROOT, linkKey); + + ResourcePath linkPath = new ResourcePath(linkKey); + // register VLAN IDs against the link + if (isEnabled(link, this::isVlanEnabled)) { + adminService.registerResources(linkPath, ENTIRE_VLAN_IDS); + } + + // register MPLS labels against the link + if (isEnabled(link, this::isMplsEnabled)) { + adminService.registerResources(linkPath, ENTIRE_MPLS_LABELS); + } + }); + } + + private void unregisterLinkResource(Link link) { + LinkKey linkKey = LinkKey.linkKey(link); + executor.submit(() -> adminService.unregisterResources(ResourcePath.ROOT, linkKey)); + } + + private boolean isEnabled(Link link, Predicate<ConnectPoint> predicate) { + return predicate.test(link.src()) && predicate.test(link.dst()); + } + + private boolean isVlanEnabled(ConnectPoint cp) { + try { + DriverHandler handler = driverService.createHandler(cp.deviceId()); + if (handler == null) { + return false; + } + + VlanQuery query = handler.behaviour(VlanQuery.class); + return query != null && query.isEnabled(cp.port()); + } catch (ItemNotFoundException e) { + return false; + } + } + + private boolean isMplsEnabled(ConnectPoint cp) { + try { + DriverHandler handler = driverService.createHandler(cp.deviceId()); + if (handler == null) { + return false; + } + + MplsQuery query = handler.behaviour(MplsQuery.class); + return query != null && query.isEnabled(cp.port()); + } catch (ItemNotFoundException e) { + return false; + } + } + + private static List<VlanId> getEntireVlans() { + return IntStream.range(0, TOTAL_VLANS) + .mapToObj(x -> VlanId.vlanId((short) x)) + .collect(Collectors.toList()); + } + + private static List<MplsLabel> getEntireMplsLabels() { + // potentially many objects are created + return IntStream.range(0, TOTAL_MPLS_LABELS) + .mapToObj(MplsLabel::mplsLabel) + .collect(Collectors.toList()); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceManager.java new file mode 100644 index 00000000..2cd1a2e0 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceManager.java @@ -0,0 +1,148 @@ +/* + * Copyright 2015 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.net.newresource.impl; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.newresource.ResourceAdminService; +import org.onosproject.net.newresource.ResourceAllocation; +import org.onosproject.net.newresource.ResourceConsumer; +import org.onosproject.net.newresource.ResourceService; +import org.onosproject.net.newresource.ResourcePath; +import org.onosproject.net.newresource.ResourceStore; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An implementation of ResourceService. + */ +@Component(immediate = true, enabled = false) +@Service +@Beta +public final class ResourceManager implements ResourceService, ResourceAdminService { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ResourceStore store; + + @Override + public List<ResourceAllocation> allocate(ResourceConsumer consumer, + List<ResourcePath> resources) { + checkNotNull(consumer); + checkNotNull(resources); + + // TODO: implement support of resource hierarchy + // allocation for a particular resource implies allocations for all of the sub-resources need to be done + + boolean success = store.allocate(resources, consumer); + if (!success) { + return ImmutableList.of(); + } + + return resources.stream() + .map(x -> new ResourceAllocation(x, consumer)) + .collect(Collectors.toList()); + } + + @Override + public boolean release(List<ResourceAllocation> allocations) { + checkNotNull(allocations); + + List<ResourcePath> resources = allocations.stream() + .map(ResourceAllocation::resource) + .collect(Collectors.toList()); + List<ResourceConsumer> consumers = allocations.stream() + .map(ResourceAllocation::consumer) + .collect(Collectors.toList()); + + return store.release(resources, consumers); + } + + @Override + public boolean release(ResourceConsumer consumer) { + checkNotNull(consumer); + + Collection<ResourceAllocation> allocations = getResourceAllocations(consumer); + return release(ImmutableList.copyOf(allocations)); + } + + @Override + public <T> Collection<ResourceAllocation> getResourceAllocations(ResourcePath parent, Class<T> cls) { + checkNotNull(parent); + checkNotNull(cls); + + Collection<ResourcePath> resources = store.getAllocatedResources(parent, cls); + List<ResourceAllocation> allocations = new ArrayList<>(resources.size()); + for (ResourcePath resource: resources) { + // We access store twice in this method, then the store may be updated by others + Optional<ResourceConsumer> consumer = store.getConsumer(resource); + if (consumer.isPresent()) { + allocations.add(new ResourceAllocation(resource, consumer.get())); + } + } + + return allocations; + } + + @Override + public Collection<ResourceAllocation> getResourceAllocations(ResourceConsumer consumer) { + checkNotNull(consumer); + + Collection<ResourcePath> resources = store.getResources(consumer); + return resources.stream() + .map(x -> new ResourceAllocation(x, consumer)) + .collect(Collectors.toList()); + } + + @Override + public boolean isAvailable(ResourcePath resource) { + checkNotNull(resource); + + Optional<ResourceConsumer> consumer = store.getConsumer(resource); + return !consumer.isPresent(); + } + + @Override + public <T> boolean registerResources(ResourcePath parent, List<T> children) { + checkNotNull(parent); + checkNotNull(children); + checkArgument(!children.isEmpty()); + + List<ResourcePath> resources = Lists.transform(children, x -> ResourcePath.child(parent, x)); + return store.register(resources); + } + + @Override + public <T> boolean unregisterResources(ResourcePath parent, List<T> children) { + checkNotNull(parent); + checkNotNull(children); + checkArgument(!children.isEmpty()); + + List<ResourcePath> resources = Lists.transform(children, x -> ResourcePath.child(parent, x)); + return store.unregister(resources); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java new file mode 100644 index 00000000..4067d017 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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.net.newresource.impl; + +import com.google.common.annotations.Beta; +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.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.DriverService; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.newresource.ResourceAdminService; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.onlab.util.Tools.groupedThreads; + +/** + * A class registering resources when they are detected. + */ +@Component(immediate = true, enabled = false) +@Beta +public final class ResourceRegistrar { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ResourceAdminService adminService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DriverService driverService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + + private DeviceListener deviceListener; + private LinkListener linkListener; + private final ExecutorService executor = + Executors.newSingleThreadExecutor(groupedThreads("onos/resource", "registrar")); + + @Activate + public void activate() { + deviceListener = new ResourceDeviceListener(adminService, executor); + deviceService.addListener(deviceListener); + linkListener = new ResourceLinkListener(adminService, driverService, executor); + linkService.addListener(linkListener); + } + + @Deactivate + public void deactivate() { + deviceService.removeListener(deviceListener); + linkService.removeListener(linkListener); + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/package-info.java new file mode 100644 index 00000000..bddfdfc1 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementation of the generic network resource subsystem. + */ +package org.onosproject.net.newresource.impl;
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java new file mode 100644 index 00000000..75239fdd --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java @@ -0,0 +1,329 @@ +/* + * Copyright 2014-2015 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.net.packet.impl; + +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.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.Device; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveContext; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.packet.DefaultPacketRequest; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketEvent; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketProvider; +import org.onosproject.net.packet.PacketProviderRegistry; +import org.onosproject.net.packet.PacketProviderService; +import org.onosproject.net.packet.PacketRequest; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.packet.PacketStore; +import org.onosproject.net.packet.PacketStoreDelegate; +import org.onosproject.net.provider.AbstractProviderRegistry; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + +/** + * Provides a basic implementation of the packet SB & NB APIs. + */ +@Component(immediate = true) +@Service +public class PacketManager + extends AbstractProviderRegistry<PacketProvider, PacketProviderService> + implements PacketService, PacketProviderRegistry { + + private final Logger log = getLogger(getClass()); + + private static final String TABLE_TYPE_MSG = + "Table Type cannot be null. For requesting packets without " + + "table hints, use other methods in the packetService API"; + + private final PacketStoreDelegate delegate = new InternalStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private FlowRuleService flowService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private PacketStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private FlowObjectiveService objectiveService; + + private ExecutorService eventHandlingExecutor; + + private final DeviceListener deviceListener = new InternalDeviceListener(); + + private final Map<Integer, PacketProcessor> processors = new ConcurrentHashMap<>(); + + private ApplicationId appId; + + @Activate + public void activate() { + eventHandlingExecutor = Executors.newSingleThreadExecutor( + groupedThreads("onos/net/packet", "event-handler")); + appId = coreService.getAppId(CoreService.CORE_APP_NAME); + store.setDelegate(delegate); + deviceService.addListener(deviceListener); + // TODO: Should we request packets for all existing devices? I believe we should. + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + deviceService.removeListener(deviceListener); + eventHandlingExecutor.shutdown(); + log.info("Stopped"); + } + + @Override + public void addProcessor(PacketProcessor processor, int priority) { + checkPermission(PACKET_EVENT); + checkNotNull(processor, "Processor cannot be null"); + processors.put(priority, processor); + } + + @Override + public void removeProcessor(PacketProcessor processor) { + checkPermission(PACKET_EVENT); + checkNotNull(processor, "Processor cannot be null"); + processors.values().remove(processor); + } + + @Override + public void requestPackets(TrafficSelector selector, PacketPriority priority, + ApplicationId appId) { + checkPermission(PACKET_READ); + checkNotNull(selector, "Selector cannot be null"); + checkNotNull(appId, "Application ID cannot be null"); + + PacketRequest request = new DefaultPacketRequest(selector, priority, appId); + if (store.requestPackets(request)) { + pushToAllDevices(request); + } + } + + @Override + public void cancelPackets(TrafficSelector selector, PacketPriority priority, + ApplicationId appId) { + checkPermission(PACKET_READ); + checkNotNull(selector, "Selector cannot be null"); + checkNotNull(appId, "Application ID cannot be null"); + + PacketRequest request = new DefaultPacketRequest(selector, priority, appId); + if (store.cancelPackets(request)) { + removeFromAllDevices(request); + } + } + + /** + * Pushes a packet request flow rule to all devices. + * + * @param request the packet request + */ + private void pushToAllDevices(PacketRequest request) { + log.debug("Pushing packet request {} to all devices", request); + for (Device device : deviceService.getDevices()) { + pushRule(device, request); + } + } + + + /** + * Removes packet request flow rule from all devices. + * + * @param request the packet request + */ + private void removeFromAllDevices(PacketRequest request) { + for (Device device : deviceService.getDevices()) { + removeRule(device, request); + } + } + + /** + * Pushes packet intercept flow rules to the device. + * + * @param device the device to push the rules to + * @param request the packet request + */ + private void pushRule(Device device, PacketRequest request) { + if (!device.type().equals(Device.Type.SWITCH)) { + return; + } + + ForwardingObjective forwarding = createBuilder(request) + .add(new ObjectiveContext() { + @Override + public void onError(Objective objective, ObjectiveError error) { + log.warn("Failed to install packet request {} to {}: {}", + request, device.id(), error); + } + }); + + objectiveService.forward(device.id(), forwarding); + } + + /** + * Removes packet intercept flow rules from the device. + * + * @param device the device to remove the rules deom + * @param request the packet request + */ + private void removeRule(Device device, PacketRequest request) { + if (!device.type().equals(Device.Type.SWITCH)) { + return; + } + + ForwardingObjective forwarding = createBuilder(request) + .remove(new ObjectiveContext() { + @Override + public void onError(Objective objective, ObjectiveError error) { + log.warn("Failed to withdraw packet request {} from {}: {}", + request, device.id(), error); + } + }); + + objectiveService.forward(device.id(), forwarding); + } + + private DefaultForwardingObjective.Builder createBuilder(PacketRequest request) { + return DefaultForwardingObjective.builder() + .withPriority(request.priority().priorityValue()) + .withSelector(request.selector()) + .fromApp(appId) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withTreatment(DefaultTrafficTreatment.builder().punt().build()) + .makePermanent(); + } + + @Override + public void emit(OutboundPacket packet) { + checkPermission(PACKET_WRITE); + checkNotNull(packet, "Packet cannot be null"); + store.emit(packet); + } + + private void localEmit(OutboundPacket packet) { + final Device device = deviceService.getDevice(packet.sendThrough()); + + if (device == null) { + return; + } + + PacketProvider packetProvider = getProvider(device.providerId()); + if (packetProvider != null) { + packetProvider.emit(packet); + } + } + + @Override + protected PacketProviderService createProviderService(PacketProvider provider) { + return new InternalPacketProviderService(provider); + } + + // Personalized packet provider service issued to the supplied provider. + private class InternalPacketProviderService + extends AbstractProviderService<PacketProvider> + implements PacketProviderService { + + protected InternalPacketProviderService(PacketProvider provider) { + super(provider); + } + + @Override + public void processPacket(PacketContext context) { + // TODO filter packets sent to processors based on registrations + for (PacketProcessor processor : processors.values()) { + processor.process(context); + } + } + + } + + /** + * Internal callback from the packet store. + */ + private class InternalStoreDelegate implements PacketStoreDelegate { + @Override + public void notify(PacketEvent event) { + localEmit(event.subject()); + } + } + + /** + * Internal listener for device service events. + */ + private class InternalDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + eventHandlingExecutor.execute(() -> { + try { + Device device = event.subject(); + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + if (deviceService.isAvailable(event.subject().id())) { + log.debug("Pushing packet requests to device {}", event.subject().id()); + for (PacketRequest request : store.existingRequests()) { + pushRule(device, request); + } + } + break; + default: + break; + } + } catch (Exception e) { + log.warn("Failed to process {}", event, e); + } + }); + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/package-info.java new file mode 100644 index 00000000..f3af4850 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for processing inbound packets and emitting outbound packets. + * Processing of inbound packets is always in the local context only, but + * emitting outbound packets allows for cluster-wide operation. + */ +package org.onosproject.net.packet.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java new file mode 100644 index 00000000..398260ff --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java @@ -0,0 +1,447 @@ +/* + * Copyright 2014-2015 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.net.proxyarp.impl; + +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.ARP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP6; +import org.onlab.packet.IPv6; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip6Address; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.packet.ndp.NeighborAdvertisement; +import org.onlab.packet.ndp.NeighborDiscoveryOptions; +import org.onlab.packet.ndp.NeighborSolicitation; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Host; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.edge.EdgePortService; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.host.HostService; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.proxyarp.ProxyArpService; +import org.onosproject.net.proxyarp.ProxyArpStore; +import org.slf4j.Logger; + +import java.nio.ByteBuffer; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.packet.VlanId.vlanId; +import static org.onosproject.net.HostId.hostId; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + +@Component(immediate = true) +@Service +public class ProxyArpManager implements ProxyArpService { + + private final Logger log = getLogger(getClass()); + + private static final String MAC_ADDR_NULL = "Mac address cannot be null."; + private static final String REQUEST_NULL = "ARP or NDP request cannot be null."; + private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request."; + private static final String NOT_ARP_REQUEST = "ARP is not a request."; + private static final String NOT_ARP_REPLY = "ARP is not a reply."; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected EdgePortService edgeService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ProxyArpStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected InterfaceService interfaceService; + + @Activate + public void activate() { + store.setDelegate(this::sendTo); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.setDelegate(null); + log.info("Stopped"); + } + + @Override + public boolean isKnown(IpAddress addr) { + checkPermission(PACKET_READ); + + checkNotNull(addr, MAC_ADDR_NULL); + Set<Host> hosts = hostService.getHostsByIp(addr); + return !hosts.isEmpty(); + } + + @Override + public void reply(Ethernet eth, ConnectPoint inPort) { + checkPermission(PACKET_WRITE); + + checkNotNull(eth, REQUEST_NULL); + + if (eth.getEtherType() == Ethernet.TYPE_ARP) { + replyArp(eth, inPort); + } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) { + replyNdp(eth, inPort); + } + } + + private void replyArp(Ethernet eth, ConnectPoint inPort) { + ARP arp = (ARP) eth.getPayload(); + checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST); + checkNotNull(inPort); + Ip4Address targetAddress = Ip4Address.valueOf(arp.getTargetProtocolAddress()); + + VlanId vlan = vlanId(eth.getVlanID()); + + if (hasIpAddress(inPort)) { + // If the request came from outside the network, only reply if it was + // for one of our external addresses. + + interfaceService.getInterfacesByPort(inPort) + .stream() + .filter(intf -> intf.ipAddresses() + .stream() + .anyMatch(ia -> ia.ipAddress().equals(targetAddress))) + .forEach(intf -> buildAndSendArp(targetAddress, intf.mac(), eth, inPort)); + + // Stop here and don't proxy ARPs if the port has an IP address + return; + } + + // See if we have the target host in the host store + + Set<Host> hosts = hostService.getHostsByIp(targetAddress); + + Host dst = null; + Host src = hostService.getHost(hostId(eth.getSourceMAC(), + vlanId(eth.getVlanID()))); + + for (Host host : hosts) { + if (host.vlan().equals(vlan)) { + dst = host; + break; + } + } + + if (src != null && dst != null) { + // We know the target host so we can respond + buildAndSendArp(targetAddress, dst.mac(), eth, inPort); + return; + } + + // If the source address matches one of our external addresses + // it could be a request from an internal host to an external + // address. Forward it over to the correct port. + Ip4Address source = + Ip4Address.valueOf(arp.getSenderProtocolAddress()); + + boolean matched = false; + Set<Interface> interfaces = interfaceService.getInterfacesByIp(source); + for (Interface intf : interfaces) { + if (intf.vlan().equals(vlan)) { + matched = true; + sendTo(eth, intf.connectPoint()); + break; + } + } + + if (matched) { + return; + } + + // The request couldn't be resolved. + // Flood the request on all ports except the incoming port. + flood(eth, inPort); + } + + private void replyNdp(Ethernet eth, ConnectPoint inPort) { + IPv6 ipv6 = (IPv6) eth.getPayload(); + ICMP6 icmpv6 = (ICMP6) ipv6.getPayload(); + NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload(); + Ip6Address targetAddress = Ip6Address.valueOf(nsol.getTargetAddress()); + + VlanId vlan = vlanId(eth.getVlanID()); + + // If the request came from outside the network, only reply if it was + // for one of our external addresses. + if (hasIpAddress(inPort)) { + interfaceService.getInterfacesByPort(inPort) + .stream() + .filter(intf -> intf.ipAddresses() + .stream() + .anyMatch(ia -> ia.ipAddress().equals(targetAddress))) + .forEach(intf -> buildAndSendNdp(targetAddress, intf.mac(), eth, inPort)); + return; + } + + // Continue with normal proxy ARP case + + Set<Host> hosts = hostService.getHostsByIp(targetAddress); + + Host dst = null; + Host src = hostService.getHost(hostId(eth.getSourceMAC(), + vlanId(eth.getVlanID()))); + + for (Host host : hosts) { + if (host.vlan().equals(vlan)) { + dst = host; + break; + } + } + + if (src != null && dst != null) { + // We know the target host so we can respond + buildAndSendNdp(targetAddress, dst.mac(), eth, inPort); + return; + } + + // If the source address matches one of our external addresses + // it could be a request from an internal host to an external + // address. Forward it over to the correct port. + Ip6Address source = + Ip6Address.valueOf(ipv6.getSourceAddress()); + + boolean matched = false; + + Set<Interface> interfaces = interfaceService.getInterfacesByIp(source); + for (Interface intf : interfaces) { + if (intf.vlan().equals(vlan)) { + matched = true; + sendTo(eth, intf.connectPoint()); + break; + } + } + + if (matched) { + return; + } + + // The request couldn't be resolved. + // Flood the request on all ports except the incoming ports. + flood(eth, inPort); + } + //TODO checkpoint + + private void buildAndSendArp(Ip4Address srcIp, MacAddress srcMac, + Ethernet request, ConnectPoint port) { + sendTo(ARP.buildArpReply(srcIp, srcMac, request), port); + } + + private void buildAndSendNdp(Ip6Address srcIp, MacAddress srcMac, + Ethernet request, ConnectPoint port) { + sendTo(buildNdpReply(srcIp, srcMac, request), port); + } + + /** + * Outputs the given packet out the given port. + * + * @param packet the packet to send + * @param outPort the port to send it out + */ + private void sendTo(Ethernet packet, ConnectPoint outPort) { + sendTo(outPort, ByteBuffer.wrap(packet.serialize())); + } + + private void sendTo(ConnectPoint outPort, ByteBuffer packet) { + if (!edgeService.isEdgePoint(outPort)) { + // Sanity check to make sure we don't send the packet out an + // internal port and create a loop (could happen due to + // misconfiguration). + return; + } + + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + builder.setOutput(outPort.port()); + packetService.emit(new DefaultOutboundPacket(outPort.deviceId(), + builder.build(), packet)); + } + + /** + * Returns whether the given port has any IP addresses configured or not. + * + * @param port the port to check + * @return true if the port has at least one IP address configured, + * otherwise false + */ + private boolean hasIpAddress(ConnectPoint port) { + return interfaceService.getInterfacesByPort(port) + .stream() + .map(intf -> intf.ipAddresses()) + .findAny() + .isPresent(); + } + + @Override + public void forward(Ethernet eth, ConnectPoint inPort) { + checkPermission(PACKET_WRITE); + + checkNotNull(eth, REQUEST_NULL); + + Host h = hostService.getHost(hostId(eth.getDestinationMAC(), + vlanId(eth.getVlanID()))); + + if (h == null) { + flood(eth, inPort); + } else { + Host subject = hostService.getHost(hostId(eth.getSourceMAC(), + vlanId(eth.getVlanID()))); + store.forward(h.location(), subject, ByteBuffer.wrap(eth.serialize())); + } + } + + @Override + public boolean handlePacket(PacketContext context) { + checkPermission(PACKET_WRITE); + + InboundPacket pkt = context.inPacket(); + Ethernet ethPkt = pkt.parsed(); + + if (ethPkt == null) { + return false; + } + if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) { + return handleArp(context, ethPkt); + } else if (ethPkt.getEtherType() == Ethernet.TYPE_IPV6) { + return handleNdp(context, ethPkt); + } + return false; + } + + private boolean handleArp(PacketContext context, Ethernet ethPkt) { + ARP arp = (ARP) ethPkt.getPayload(); + + if (arp.getOpCode() == ARP.OP_REPLY) { + forward(ethPkt, context.inPacket().receivedFrom()); + } else if (arp.getOpCode() == ARP.OP_REQUEST) { + reply(ethPkt, context.inPacket().receivedFrom()); + } else { + return false; + } + context.block(); + return true; + } + + private boolean handleNdp(PacketContext context, Ethernet ethPkt) { + IPv6 ipv6 = (IPv6) ethPkt.getPayload(); + + if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) { + return false; + } + ICMP6 icmpv6 = (ICMP6) ipv6.getPayload(); + if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) { + forward(ethPkt, context.inPacket().receivedFrom()); + } else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) { + reply(ethPkt, context.inPacket().receivedFrom()); + } else { + return false; + } + context.block(); + return true; + } + + /** + * Flood the arp request at all edges in the network. + * + * @param request the arp request + * @param inPort the connect point the arp request was received on + */ + private void flood(Ethernet request, ConnectPoint inPort) { + TrafficTreatment.Builder builder = null; + ByteBuffer buf = ByteBuffer.wrap(request.serialize()); + + for (ConnectPoint connectPoint : edgeService.getEdgePoints()) { + if (hasIpAddress(connectPoint) || connectPoint.equals(inPort)) { + continue; + } + + builder = DefaultTrafficTreatment.builder(); + builder.setOutput(connectPoint.port()); + packetService.emit(new DefaultOutboundPacket(connectPoint.deviceId(), + builder.build(), buf)); + } + } + + /** + * Builds an Neighbor Discovery reply based on a request. + * + * @param srcIp the IP address to use as the reply source + * @param srcMac the MAC address to use as the reply source + * @param request the Neighbor Solicitation request we got + * @return an Ethernet frame containing the Neighbor Advertisement reply + */ + private Ethernet buildNdpReply(Ip6Address srcIp, MacAddress srcMac, + Ethernet request) { + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(request.getSourceMAC()); + eth.setSourceMACAddress(srcMac); + eth.setEtherType(Ethernet.TYPE_IPV6); + eth.setVlanID(request.getVlanID()); + + IPv6 requestIp = (IPv6) request.getPayload(); + IPv6 ipv6 = new IPv6(); + ipv6.setSourceAddress(srcIp.toOctets()); + ipv6.setDestinationAddress(requestIp.getSourceAddress()); + ipv6.setHopLimit((byte) 255); + + ICMP6 icmp6 = new ICMP6(); + icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT); + icmp6.setIcmpCode((byte) 0); + + NeighborAdvertisement nadv = new NeighborAdvertisement(); + nadv.setTargetAddress(srcIp.toOctets()); + nadv.setSolicitedFlag((byte) 1); + nadv.setOverrideFlag((byte) 1); + nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + srcMac.toBytes()); + + icmp6.setPayload(nadv); + ipv6.setPayload(icmp6); + eth.setPayload(ipv6); + return eth; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/package-info.java new file mode 100644 index 00000000..9ce268fb --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for responding to arp requests. + */ +package org.onosproject.net.proxyarp.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/DeviceResourceManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/DeviceResourceManager.java new file mode 100644 index 00000000..62b4112b --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/DeviceResourceManager.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015 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.net.resource.impl; + +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.onosproject.net.Port; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentId; +import org.onosproject.net.resource.device.DeviceResourceService; +import org.onosproject.net.resource.device.DeviceResourceStore; +import org.slf4j.Logger; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provides basic implementation of device resources allocation. + */ +@Component(immediate = true) +@Service +public class DeviceResourceManager implements DeviceResourceService { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private DeviceResourceStore store; + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public boolean requestPorts(Set<Port> ports, Intent intent) { + checkNotNull(intent); + + return store.allocatePorts(ports, intent.id()); + } + + @Override + public Set<Port> getAllocations(IntentId intentId) { + return store.getAllocations(intentId); + } + + @Override + public IntentId getAllocations(Port port) { + return store.getAllocations(port); + } + + @Override + public void releaseMapping(IntentId intentId) { + store.releaseMapping(intentId); + } + + @Override + public boolean requestMapping(IntentId keyIntentId, IntentId valIntentId) { + return store.allocateMapping(keyIntentId, valIntentId); + } + + @Override + public Set<IntentId> getMapping(IntentId intentId) { + return store.getMapping(intentId); + } + + @Override + public void releasePorts(IntentId intentId) { + store.releasePorts(intentId); + } + + private Port getTypedPort(Set<Port> ports, Port.Type type) { + for (Port port : ports) { + if (port.type() == type) { + return port; + } + } + + return null; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/LinkResourceManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/LinkResourceManager.java new file mode 100644 index 00000000..8b9952ed --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/LinkResourceManager.java @@ -0,0 +1,293 @@ +/* + * Copyright 2014-2015 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.net.resource.impl; + +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.event.AbstractListenerManager; +import org.onosproject.net.Link; +import org.onosproject.net.intent.IntentId; +import org.onosproject.net.resource.ResourceAllocation; +import org.onosproject.net.resource.ResourceRequest; +import org.onosproject.net.resource.ResourceType; +import org.onosproject.net.resource.link.BandwidthResourceAllocation; +import org.onosproject.net.resource.link.BandwidthResourceRequest; +import org.onosproject.net.resource.link.DefaultLinkResourceAllocations; +import org.onosproject.net.resource.link.LambdaResource; +import org.onosproject.net.resource.link.LambdaResourceAllocation; +import org.onosproject.net.resource.link.LambdaResourceRequest; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.resource.link.LinkResourceEvent; +import org.onosproject.net.resource.link.LinkResourceListener; +import org.onosproject.net.resource.link.LinkResourceRequest; +import org.onosproject.net.resource.link.LinkResourceService; +import org.onosproject.net.resource.link.LinkResourceStore; +import org.onosproject.net.resource.link.LinkResourceStoreDelegate; +import org.onosproject.net.resource.link.MplsLabel; +import org.onosproject.net.resource.link.MplsLabelResourceAllocation; +import org.onosproject.net.resource.link.MplsLabelResourceRequest; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Provides basic implementation of link resources allocation. + */ +@Component(immediate = true) +@Service +public class LinkResourceManager + extends AbstractListenerManager<LinkResourceEvent, LinkResourceListener> + implements LinkResourceService { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private LinkResourceStore store; + + @Activate + public void activate() { + eventDispatcher.addSink(LinkResourceEvent.class, listenerRegistry); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + eventDispatcher.removeSink(LinkResourceEvent.class); + log.info("Stopped"); + } + + /** + * Returns available lambdas on specified link. + * + * @param link the link + * @return available lambdas on specified link + */ + private Set<LambdaResource> getAvailableLambdas(Link link) { + checkNotNull(link); + Set<ResourceAllocation> resAllocs = store.getFreeResources(link); + if (resAllocs == null) { + return Collections.emptySet(); + } + Set<LambdaResource> lambdas = new HashSet<>(); + for (ResourceAllocation res : resAllocs) { + if (res.type() == ResourceType.LAMBDA) { + lambdas.add(((LambdaResourceAllocation) res).lambda()); + } + } + return lambdas; + } + + + /** + * Returns available lambdas on specified links. + * + * @param links the links + * @return available lambdas on specified links + */ + private Iterable<LambdaResource> getAvailableLambdas(Iterable<Link> links) { + checkNotNull(links); + Iterator<Link> i = links.iterator(); + checkArgument(i.hasNext()); + Set<LambdaResource> lambdas = new HashSet<>(getAvailableLambdas(i.next())); + while (i.hasNext()) { + lambdas.retainAll(getAvailableLambdas(i.next())); + } + return lambdas; + } + + + /** + * Returns available MPLS label on specified link. + * + * @param link the link + * @return available MPLS labels on specified link + */ + private Iterable<MplsLabel> getAvailableMplsLabels(Link link) { + Set<ResourceAllocation> resAllocs = store.getFreeResources(link); + if (resAllocs == null) { + return Collections.emptySet(); + } + Set<MplsLabel> mplsLabels = new HashSet<>(); + for (ResourceAllocation res : resAllocs) { + if (res.type() == ResourceType.MPLS_LABEL) { + + mplsLabels.add(((MplsLabelResourceAllocation) res).mplsLabel()); + } + } + + return mplsLabels; + } + + @Override + public LinkResourceAllocations requestResources(LinkResourceRequest req) { + checkPermission(LINK_WRITE); + + // TODO Concatenate multiple bandwidth requests. + // TODO Support multiple lambda resource requests. + // TODO Throw appropriate exception. + Set<ResourceAllocation> allocs = new HashSet<>(); + Map<Link, Set<ResourceAllocation>> allocsPerLink = new HashMap<>(); + for (ResourceRequest r : req.resources()) { + switch (r.type()) { + case BANDWIDTH: + BandwidthResourceRequest br = (BandwidthResourceRequest) r; + allocs.add(new BandwidthResourceAllocation(br.bandwidth())); + break; + case LAMBDA: + Iterator<LambdaResource> lambdaIterator = + getAvailableLambdas(req.links()).iterator(); + if (lambdaIterator.hasNext()) { + allocs.add(new LambdaResourceAllocation(lambdaIterator.next())); + } else { + log.info("Failed to allocate lambda resource."); + return null; + } + break; + case MPLS_LABEL: + for (Link link : req.links()) { + if (allocsPerLink.get(link) == null) { + allocsPerLink.put(link, new HashSet<>()); + } + Iterator<MplsLabel> mplsIter = getAvailableMplsLabels(link) + .iterator(); + if (mplsIter.hasNext()) { + allocsPerLink.get(link) + .add(new MplsLabelResourceAllocation(mplsIter + .next())); + } else { + log.info("Failed to allocate MPLS resource."); + break; + } + } + break; + default: + break; + } + } + + Map<Link, Set<ResourceAllocation>> allocations = new HashMap<>(); + for (Link link : req.links()) { + allocations.put(link, new HashSet<>(allocs)); + Set<ResourceAllocation> linkAllocs = allocsPerLink.get(link); + if (linkAllocs != null) { + allocations.get(link).addAll(linkAllocs); + } + } + LinkResourceAllocations result = + new DefaultLinkResourceAllocations(req, allocations); + store.allocateResources(result); + return result; + + } + + @Override + public void releaseResources(LinkResourceAllocations allocations) { + checkPermission(LINK_WRITE); + final LinkResourceEvent event = store.releaseResources(allocations); + if (event != null) { + post(event); + } + } + + @Override + public LinkResourceAllocations updateResources(LinkResourceRequest req, + LinkResourceAllocations oldAllocations) { + checkPermission(LINK_WRITE); + releaseResources(oldAllocations); + return requestResources(req); + } + + @Override + public Iterable<LinkResourceAllocations> getAllocations() { + checkPermission(LINK_READ); + return store.getAllocations(); + } + + @Override + public Iterable<LinkResourceAllocations> getAllocations(Link link) { + checkPermission(LINK_READ); + return store.getAllocations(link); + } + + @Override + public LinkResourceAllocations getAllocations(IntentId intentId) { + checkPermission(LINK_READ); + return store.getAllocations(intentId); + } + + @Override + public Iterable<ResourceRequest> getAvailableResources(Link link) { + checkPermission(LINK_READ); + + Set<ResourceAllocation> freeRes = store.getFreeResources(link); + Set<ResourceRequest> result = new HashSet<>(); + for (ResourceAllocation alloc : freeRes) { + switch (alloc.type()) { + case BANDWIDTH: + result.add(new BandwidthResourceRequest( + ((BandwidthResourceAllocation) alloc).bandwidth())); + break; + case LAMBDA: + result.add(new LambdaResourceRequest()); + break; + case MPLS_LABEL: + result.add(new MplsLabelResourceRequest()); + break; + default: + break; + } + } + return result; + } + + @Override + public Iterable<ResourceRequest> getAvailableResources(Link link, + LinkResourceAllocations allocations) { + checkPermission(LINK_READ); + + Set<ResourceAllocation> allocatedRes = allocations.getResourceAllocation(link); + Set<ResourceRequest> result = Sets.newHashSet(getAvailableResources(link)); + result.removeAll(allocatedRes); + return result; + } + + /** + * Store delegate to re-post events emitted from the store. + */ + private class InternalStoreDelegate implements LinkResourceStoreDelegate { + @Override + public void notify(LinkResourceEvent event) { + post(event); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/package-info.java new file mode 100644 index 00000000..fd0bbdec --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Services for reserving network resources, e.g. bandwidth, lambdas. + */ +package org.onosproject.net.resource.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/StatisticManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/StatisticManager.java new file mode 100644 index 00000000..996ad14e --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/StatisticManager.java @@ -0,0 +1,379 @@ +/* + * Copyright 2014 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.net.statistic.impl; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableSet; +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.onosproject.core.ApplicationId; +import org.onosproject.core.GroupId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.Path; + +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleEvent; +import org.onosproject.net.flow.FlowRuleListener; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.statistic.DefaultLoad; +import org.onosproject.net.statistic.Load; +import org.onosproject.net.statistic.StatisticService; +import org.onosproject.net.statistic.StatisticStore; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Provides an implementation of the Statistic Service. + */ +@Component(immediate = true) +@Service +public class StatisticManager implements StatisticService { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowRuleService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected StatisticStore statisticStore; + + + private final InternalFlowRuleListener listener = new InternalFlowRuleListener(); + + @Activate + public void activate() { + flowRuleService.addListener(listener); + log.info("Started"); + + } + + @Deactivate + public void deactivate() { + flowRuleService.removeListener(listener); + log.info("Stopped"); + } + + @Override + public Load load(Link link) { + checkPermission(STATISTIC_READ); + + return load(link.src()); + } + + @Override + public Load load(Link link, ApplicationId appId, Optional<GroupId> groupId) { + checkPermission(STATISTIC_READ); + + Statistics stats = getStatistics(link.src()); + if (!stats.isValid()) { + return new DefaultLoad(); + } + + ImmutableSet<FlowEntry> current = FluentIterable.from(stats.current()) + .filter(hasApplicationId(appId)) + .filter(hasGroupId(groupId)) + .toSet(); + ImmutableSet<FlowEntry> previous = FluentIterable.from(stats.previous()) + .filter(hasApplicationId(appId)) + .filter(hasGroupId(groupId)) + .toSet(); + + return new DefaultLoad(aggregate(current), aggregate(previous)); + } + + @Override + public Load load(ConnectPoint connectPoint) { + checkPermission(STATISTIC_READ); + + return loadInternal(connectPoint); + } + + @Override + public Link max(Path path) { + checkPermission(STATISTIC_READ); + + if (path.links().isEmpty()) { + return null; + } + Load maxLoad = new DefaultLoad(); + Link maxLink = null; + for (Link link : path.links()) { + Load load = loadInternal(link.src()); + if (load.rate() > maxLoad.rate()) { + maxLoad = load; + maxLink = link; + } + } + return maxLink; + } + + @Override + public Link min(Path path) { + checkPermission(STATISTIC_READ); + + if (path.links().isEmpty()) { + return null; + } + Load minLoad = new DefaultLoad(); + Link minLink = null; + for (Link link : path.links()) { + Load load = loadInternal(link.src()); + if (load.rate() < minLoad.rate()) { + minLoad = load; + minLink = link; + } + } + return minLink; + } + + @Override + public FlowRule highestHitter(ConnectPoint connectPoint) { + checkPermission(STATISTIC_READ); + + Set<FlowEntry> hitters = statisticStore.getCurrentStatistic(connectPoint); + if (hitters.isEmpty()) { + return null; + } + + FlowEntry max = hitters.iterator().next(); + for (FlowEntry entry : hitters) { + if (entry.bytes() > max.bytes()) { + max = entry; + } + } + return max; + } + + private Load loadInternal(ConnectPoint connectPoint) { + Statistics stats = getStatistics(connectPoint); + if (!stats.isValid()) { + return new DefaultLoad(); + } + + return new DefaultLoad(aggregate(stats.current), aggregate(stats.previous)); + } + + /** + * Returns statistics of the specified port. + * + * @param connectPoint port to query + * @return statistics + */ + private Statistics getStatistics(ConnectPoint connectPoint) { + Set<FlowEntry> current; + Set<FlowEntry> previous; + synchronized (statisticStore) { + current = getCurrentStatistic(connectPoint); + previous = getPreviousStatistic(connectPoint); + } + + return new Statistics(current, previous); + } + + /** + * Returns the current statistic of the specified port. + + * @param connectPoint port to query + * @return set of flow entries + */ + private Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) { + Set<FlowEntry> stats = statisticStore.getCurrentStatistic(connectPoint); + if (stats == null) { + return Collections.emptySet(); + } else { + return stats; + } + } + + /** + * Returns the previous statistic of the specified port. + * + * @param connectPoint port to query + * @return set of flow entries + */ + private Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) { + Set<FlowEntry> stats = statisticStore.getPreviousStatistic(connectPoint); + if (stats == null) { + return Collections.emptySet(); + } else { + return stats; + } + } + + // TODO: make aggregation function generic by passing a function + // (applying Java 8 Stream API?) + /** + * Aggregates a set of values. + * @param values the values to aggregate + * @return a long value + */ + private long aggregate(Set<FlowEntry> values) { + long sum = 0; + for (FlowEntry f : values) { + sum += f.bytes(); + } + return sum; + } + + /** + * Internal flow rule event listener. + */ + private class InternalFlowRuleListener implements FlowRuleListener { + + @Override + public void event(FlowRuleEvent event) { + FlowRule rule = event.subject(); + switch (event.type()) { + case RULE_ADDED: + case RULE_UPDATED: + if (rule instanceof FlowEntry) { + statisticStore.addOrUpdateStatistic((FlowEntry) rule); + } + break; + case RULE_ADD_REQUESTED: + statisticStore.prepareForStatistics(rule); + break; + case RULE_REMOVE_REQUESTED: + statisticStore.removeFromStatistics(rule); + break; + case RULE_REMOVED: + break; + default: + log.warn("Unknown flow rule event {}", event); + } + } + } + + /** + * Internal data class holding two set of flow entries. + */ + private static class Statistics { + private final ImmutableSet<FlowEntry> current; + private final ImmutableSet<FlowEntry> previous; + + public Statistics(Set<FlowEntry> current, Set<FlowEntry> previous) { + this.current = ImmutableSet.copyOf(checkNotNull(current)); + this.previous = ImmutableSet.copyOf(checkNotNull(previous)); + } + + /** + * Returns flow entries as the current value. + * + * @return flow entries as the current value + */ + public ImmutableSet<FlowEntry> current() { + return current; + } + + /** + * Returns flow entries as the previous value. + * + * @return flow entries as the previous value + */ + public ImmutableSet<FlowEntry> previous() { + return previous; + } + + /** + * Validates values are not empty. + * + * @return false if either of the sets is empty. Otherwise, true. + */ + public boolean isValid() { + return !(current.isEmpty() || previous.isEmpty()); + } + + @Override + public int hashCode() { + return Objects.hash(current, previous); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Statistics)) { + return false; + } + final Statistics other = (Statistics) obj; + return Objects.equals(this.current, other.current) && Objects.equals(this.previous, other.previous); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("current", current) + .add("previous", previous) + .toString(); + } + } + + /** + * Creates a predicate that checks the application ID of a flow entry is the same as + * the specified application ID. + * + * @param appId application ID to be checked + * @return predicate + */ + private static Predicate<FlowEntry> hasApplicationId(ApplicationId appId) { + return new Predicate<FlowEntry>() { + @Override + public boolean apply(FlowEntry flowEntry) { + return flowEntry.appId() == appId.id(); + } + }; + } + + /** + * Create a predicate that checks the group ID of a flow entry is the same as + * the specified group ID. + * + * @param groupId group ID to be checked + * @return predicate + */ + private static Predicate<FlowEntry> hasGroupId(Optional<GroupId> groupId) { + return new Predicate<FlowEntry>() { + @Override + public boolean apply(FlowEntry flowEntry) { + if (!groupId.isPresent()) { + return false; + } + // FIXME: The left hand type and right hand type don't match + // FlowEntry.groupId() still returns a short value, not int. + return flowEntry.groupId().equals(groupId.get()); + } + }; + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/package-info.java new file mode 100644 index 00000000..b1c9170a --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for responding to statistical inquiries. + */ +package org.onosproject.net.statistic.impl; diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java new file mode 100644 index 00000000..20a5ad36 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java @@ -0,0 +1,287 @@ +/* + * Copyright 2014-2015 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.net.topology.impl; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.onlab.util.Tools.get; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.core.CoreService.CORE_PROVIDER_ID; +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED; +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED; +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.List; +import java.util.Timer; +import java.util.concurrent.ExecutorService; + +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.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.AbstractAccumulator; +import org.onlab.util.Accumulator; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.event.Event; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.topology.DefaultGraphDescription; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.TopologyProvider; +import org.onosproject.net.topology.TopologyProviderRegistry; +import org.onosproject.net.topology.TopologyProviderService; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import com.google.common.collect.ImmutableList; + +/** + * Default implementation of a network topology provider that feeds off + * device and link subsystem events to trigger assembly and computation of + * new topology snapshots. + */ +@Component(immediate = true) +@Service +public class DefaultTopologyProvider extends AbstractProvider + implements TopologyProvider { + + private static final int MAX_THREADS = 8; + private static final int DEFAULT_MAX_EVENTS = 1000; + private static final int DEFAULT_MAX_IDLE_MS = 10; + private static final int DEFAULT_MAX_BATCH_MS = 50; + + // FIXME: Replace with a system-wide timer instance; + // TODO: Convert to use HashedWheelTimer or produce a variant of that; then decide which we want to adopt + private static final Timer TIMER = new Timer("onos-topo-event-batching"); + + @Property(name = "maxEvents", intValue = DEFAULT_MAX_EVENTS, + label = "Maximum number of events to accumulate") + private int maxEvents = DEFAULT_MAX_EVENTS; + + @Property(name = "maxIdleMs", intValue = DEFAULT_MAX_IDLE_MS, + label = "Maximum number of millis between events") + private int maxIdleMs = DEFAULT_MAX_IDLE_MS; + + @Property(name = "maxBatchMs", intValue = DEFAULT_MAX_BATCH_MS, + label = "Maximum number of millis for whole batch") + private int maxBatchMs = DEFAULT_MAX_BATCH_MS; + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private volatile boolean isStarted = false; + + private TopologyProviderService providerService; + private final DeviceListener deviceListener = new InternalDeviceListener(); + private final LinkListener linkListener = new InternalLinkListener(); + + private Accumulator<Event> accumulator; + private ExecutorService executor; + + /** + * Creates a provider with the supplier identifier. + */ + public DefaultTopologyProvider() { + super(CORE_PROVIDER_ID); + } + + @Activate + public synchronized void activate(ComponentContext context) { + cfgService.registerProperties(DefaultTopologyProvider.class); + executor = newFixedThreadPool(MAX_THREADS, groupedThreads("onos/topo", "build-%d")); + accumulator = new TopologyChangeAccumulator(); + logConfig("Configured"); + + modified(context); + + providerService = providerRegistry.register(this); + deviceService.addListener(deviceListener); + linkService.addListener(linkListener); + + isStarted = true; + triggerRecompute(); + log.info("Started"); + } + + @Deactivate + public synchronized void deactivate(ComponentContext context) { + cfgService.unregisterProperties(DefaultTopologyProvider.class, false); + isStarted = false; + + deviceService.removeListener(deviceListener); + linkService.removeListener(linkListener); + providerRegistry.unregister(this); + providerService = null; + + executor.shutdownNow(); + executor = null; + + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + accumulator = new TopologyChangeAccumulator(); + logConfig("Reconfigured"); + return; + } + + Dictionary<?, ?> properties = context.getProperties(); + int newMaxEvents, newMaxBatchMs, newMaxIdleMs; + try { + String s = get(properties, "maxEvents"); + newMaxEvents = isNullOrEmpty(s) ? maxEvents : Integer.parseInt(s.trim()); + + s = get(properties, "maxBatchMs"); + newMaxBatchMs = isNullOrEmpty(s) ? maxBatchMs : Integer.parseInt(s.trim()); + + s = get(properties, "maxIdleMs"); + newMaxIdleMs = isNullOrEmpty(s) ? maxIdleMs : Integer.parseInt(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + newMaxEvents = DEFAULT_MAX_EVENTS; + newMaxBatchMs = DEFAULT_MAX_BATCH_MS; + newMaxIdleMs = DEFAULT_MAX_IDLE_MS; + } + + if (newMaxEvents != maxEvents || newMaxBatchMs != maxBatchMs || newMaxIdleMs != maxIdleMs) { + maxEvents = newMaxEvents; + maxBatchMs = newMaxBatchMs; + maxIdleMs = newMaxIdleMs; + accumulator = maxEvents > 1 ? new TopologyChangeAccumulator() : null; + logConfig("Reconfigured"); + } + } + + private void logConfig(String prefix) { + log.info("{} with maxEvents = {}; maxBatchMs = {}; maxIdleMs = {}; accumulator={}", + prefix, maxEvents, maxBatchMs, maxIdleMs, accumulator != null); + } + + + @Override + public void triggerRecompute() { + triggerTopologyBuild(Collections.<Event>emptyList()); + } + + /** + * Triggers assembly of topology data citing the specified events as the + * reason. + * + * @param reasons events which triggered the topology change + */ + private synchronized void triggerTopologyBuild(List<Event> reasons) { + if (executor != null) { + executor.execute(new TopologyBuilderTask(reasons)); + } + } + + // Builds the topology using the latest device and link information + // and citing the specified events as reasons for the change. + private void buildTopology(List<Event> reasons) { + if (isStarted) { + GraphDescription desc = + new DefaultGraphDescription(System.nanoTime(), + System.currentTimeMillis(), + deviceService.getAvailableDevices(), + linkService.getActiveLinks()); + providerService.topologyChanged(desc, reasons); + } + } + + private void processEvent(Event event) { + if (accumulator != null) { + accumulator.add(event); + } else { + triggerTopologyBuild(ImmutableList.of(event)); + } + } + + // Callback for device events + private class InternalDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + DeviceEvent.Type type = event.type(); + if (type == DEVICE_ADDED || type == DEVICE_REMOVED || + type == DEVICE_AVAILABILITY_CHANGED) { + processEvent(event); + } + } + } + + // Callback for link events + private class InternalLinkListener implements LinkListener { + @Override + public void event(LinkEvent event) { + processEvent(event); + } + } + + // Event accumulator for paced triggering of topology assembly. + private class TopologyChangeAccumulator extends AbstractAccumulator<Event> { + TopologyChangeAccumulator() { + super(TIMER, maxEvents, maxBatchMs, maxIdleMs); + } + + @Override + public void processItems(List<Event> items) { + triggerTopologyBuild(items); + } + } + + // Task for building topology data in a separate thread. + private class TopologyBuilderTask implements Runnable { + private final List<Event> reasons; + + public TopologyBuilderTask(List<Event> reasons) { + this.reasons = reasons; + } + + @Override + public void run() { + try { + buildTopology(reasons); + } catch (Exception e) { + log.warn("Unable to compute topology", e); + } + } + } + +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/PathManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/PathManager.java new file mode 100644 index 00000000..a238c7fb --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/PathManager.java @@ -0,0 +1,190 @@ +/* + * Copyright 2014 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.net.topology.impl; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultEdgeLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.DeviceId; +import org.onosproject.net.EdgeLink; +import org.onosproject.net.ElementId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.host.HostService; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.PathService; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyService; +import org.slf4j.Logger; + +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Provides implementation of a path selection service atop the current + * topology and host services. + */ +@Component(immediate = true) +@Service +public class PathManager implements PathService { + + private static final String ELEMENT_ID_NULL = "Element ID cannot be null"; + + private static final ProviderId PID = new ProviderId("core", "org.onosproject.core"); + private static final PortNumber P0 = PortNumber.portNumber(0); + + private static final EdgeLink NOT_HOST = new NotHost(); + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public Set<Path> getPaths(ElementId src, ElementId dst) { + checkPermission(TOPOLOGY_READ); + + return getPaths(src, dst, null); + } + + @Override + public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) { + checkPermission(TOPOLOGY_READ); + + checkNotNull(src, ELEMENT_ID_NULL); + checkNotNull(dst, ELEMENT_ID_NULL); + + // Get the source and destination edge locations + EdgeLink srcEdge = getEdgeLink(src, true); + EdgeLink dstEdge = getEdgeLink(dst, false); + + // If either edge is null, bail with no paths. + if (srcEdge == null || dstEdge == null) { + return ImmutableSet.of(); + } + + DeviceId srcDevice = srcEdge != NOT_HOST ? srcEdge.dst().deviceId() : (DeviceId) src; + DeviceId dstDevice = dstEdge != NOT_HOST ? dstEdge.src().deviceId() : (DeviceId) dst; + + // If the source and destination are on the same edge device, there + // is just one path, so build it and return it. + if (srcDevice.equals(dstDevice)) { + return edgeToEdgePaths(srcEdge, dstEdge); + } + + // Otherwise get all paths between the source and destination edge + // devices. + Topology topology = topologyService.currentTopology(); + Set<Path> paths = weight == null ? + topologyService.getPaths(topology, srcDevice, dstDevice) : + topologyService.getPaths(topology, srcDevice, dstDevice, weight); + + return edgeToEdgePaths(srcEdge, dstEdge, paths); + } + + // Finds the host edge link if the element ID is a host id of an existing + // host. Otherwise, if the host does not exist, it returns null and if + // the element ID is not a host ID, returns NOT_HOST edge link. + private EdgeLink getEdgeLink(ElementId elementId, boolean isIngress) { + if (elementId instanceof HostId) { + // Resolve the host, return null. + Host host = hostService.getHost((HostId) elementId); + if (host == null) { + return null; + } + return new DefaultEdgeLink(PID, new ConnectPoint(elementId, P0), + host.location(), isIngress); + } + return NOT_HOST; + } + + // Produces a set of edge-to-edge paths using the set of infrastructure + // paths and the given edge links. + private Set<Path> edgeToEdgePaths(EdgeLink srcLink, EdgeLink dstLink) { + Set<Path> endToEndPaths = Sets.newHashSetWithExpectedSize(1); + endToEndPaths.add(edgeToEdgePath(srcLink, dstLink, null)); + return endToEndPaths; + } + + // Produces a set of edge-to-edge paths using the set of infrastructure + // paths and the given edge links. + private Set<Path> edgeToEdgePaths(EdgeLink srcLink, EdgeLink dstLink, Set<Path> paths) { + Set<Path> endToEndPaths = Sets.newHashSetWithExpectedSize(paths.size()); + for (Path path : paths) { + endToEndPaths.add(edgeToEdgePath(srcLink, dstLink, path)); + } + return endToEndPaths; + } + + // Produces a direct edge-to-edge path. + private Path edgeToEdgePath(EdgeLink srcLink, EdgeLink dstLink, Path path) { + List<Link> links = Lists.newArrayListWithCapacity(2); + // Add source and destination edge links only if they are real and + // add the infrastructure path only if it is not null. + if (srcLink != NOT_HOST) { + links.add(srcLink); + } + if (path != null) { + links.addAll(path.links()); + } + if (dstLink != NOT_HOST) { + links.add(dstLink); + } + return new DefaultPath(PID, links, 2); + } + + // Special value for edge link to represent that this is really not an + // edge link since the src or dst are really an infrastructure device. + private static class NotHost extends DefaultEdgeLink implements EdgeLink { + NotHost() { + super(PID, new ConnectPoint(HostId.NONE, P0), + new HostLocation(DeviceId.NONE, P0, 0L), false); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/TopologyManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/TopologyManager.java new file mode 100644 index 00000000..04c4f1c1 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/TopologyManager.java @@ -0,0 +1,215 @@ +/* + * Copyright 2014 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.net.topology.impl; + +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.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.event.Event; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.topology.ClusterId; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyCluster; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyGraph; +import org.onosproject.net.topology.TopologyListener; +import org.onosproject.net.topology.TopologyProvider; +import org.onosproject.net.topology.TopologyProviderRegistry; +import org.onosproject.net.topology.TopologyProviderService; +import org.onosproject.net.topology.TopologyService; +import org.onosproject.net.topology.TopologyStore; +import org.onosproject.net.topology.TopologyStoreDelegate; +import org.slf4j.Logger; + +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Provides basic implementation of the topology SB & NB APIs. + */ +@Component(immediate = true) +@Service +public class TopologyManager + extends AbstractListenerProviderRegistry<TopologyEvent, TopologyListener, + TopologyProvider, TopologyProviderService> + implements TopologyService, TopologyProviderRegistry { + + public static final String TOPOLOGY_NULL = "Topology cannot be null"; + private static final String DEVICE_ID_NULL = "Device ID cannot be null"; + private static final String CLUSTER_ID_NULL = "Cluster ID cannot be null"; + private static final String CLUSTER_NULL = "Topology cluster cannot be null"; + public static final String CONNECTION_POINT_NULL = "Connection point cannot be null"; + + private final Logger log = getLogger(getClass()); + + private TopologyStoreDelegate delegate = new InternalStoreDelegate(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyStore store; + + @Activate + public void activate() { + store.setDelegate(delegate); + eventDispatcher.addSink(TopologyEvent.class, listenerRegistry); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + store.unsetDelegate(delegate); + eventDispatcher.removeSink(TopologyEvent.class); + log.info("Stopped"); + } + + @Override + public Topology currentTopology() { + checkPermission(TOPOLOGY_READ); + return store.currentTopology(); + } + + @Override + public boolean isLatest(Topology topology) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + return store.isLatest(topology); + } + + @Override + public Set<TopologyCluster> getClusters(Topology topology) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + return store.getClusters(topology); + } + + @Override + public TopologyCluster getCluster(Topology topology, ClusterId clusterId) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(topology, CLUSTER_ID_NULL); + return store.getCluster(topology, clusterId); + } + + @Override + public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(topology, CLUSTER_NULL); + return store.getClusterDevices(topology, cluster); + } + + @Override + public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(topology, CLUSTER_NULL); + return store.getClusterLinks(topology, cluster); + } + + @Override + public TopologyGraph getGraph(Topology topology) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + return store.getGraph(topology); + } + + @Override + public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(src, DEVICE_ID_NULL); + checkNotNull(dst, DEVICE_ID_NULL); + return store.getPaths(topology, src, dst); + } + + @Override + public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst, LinkWeight weight) { + checkPermission(TOPOLOGY_READ); + + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(src, DEVICE_ID_NULL); + checkNotNull(dst, DEVICE_ID_NULL); + checkNotNull(weight, "Link weight cannot be null"); + return store.getPaths(topology, src, dst, weight); + } + + @Override + public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(connectPoint, CONNECTION_POINT_NULL); + return store.isInfrastructure(topology, connectPoint); + } + + @Override + public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) { + checkPermission(TOPOLOGY_READ); + checkNotNull(topology, TOPOLOGY_NULL); + checkNotNull(connectPoint, CONNECTION_POINT_NULL); + return store.isBroadcastPoint(topology, connectPoint); + } + + // Personalized host provider service issued to the supplied provider. + @Override + protected TopologyProviderService createProviderService(TopologyProvider provider) { + return new InternalTopologyProviderService(provider); + } + + private class InternalTopologyProviderService + extends AbstractProviderService<TopologyProvider> + implements TopologyProviderService { + + InternalTopologyProviderService(TopologyProvider provider) { + super(provider); + } + + @Override + public void topologyChanged(GraphDescription topoDescription, + List<Event> reasons) { + checkNotNull(topoDescription, "Topology description cannot be null"); + + TopologyEvent event = store.updateTopology(provider().id(), + topoDescription, reasons); + if (event != null) { + log.info("Topology {} changed", event.subject()); + post(event); + } + } + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements TopologyStoreDelegate { + @Override + public void notify(TopologyEvent event) { + post(event); + } + } +} diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/package-info.java new file mode 100644 index 00000000..586bbf3b --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Core subsystem for tracking global & consistent topology graph views. + */ +package org.onosproject.net.topology.impl; diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java new file mode 100644 index 00000000..1ce31ac3 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 2015 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.app.impl; + +import com.google.common.collect.ImmutableSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.app.ApplicationEvent; +import org.onosproject.app.ApplicationListener; +import org.onosproject.app.ApplicationState; +import org.onosproject.app.ApplicationStoreAdapter; +import org.onosproject.common.app.ApplicationArchive; +import org.onosproject.core.Application; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.DefaultApplication; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.common.event.impl.TestEventDispatcher; + +import java.io.InputStream; +import java.net.URI; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.onosproject.app.ApplicationEvent.Type.*; +import static org.onosproject.app.ApplicationState.ACTIVE; +import static org.onosproject.app.ApplicationState.INSTALLED; +import static org.onosproject.app.DefaultApplicationDescriptionTest.*; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; + +/** + * Test of the application manager implementation. + */ +public class ApplicationManagerTest { + + public static final DefaultApplicationId APP_ID = new DefaultApplicationId(1, APP_NAME); + + private ApplicationManager mgr = new ApplicationManager(); + private ApplicationListener listener = new TestListener(); + + @Before + public void setUp() { + injectEventDispatcher(mgr, new TestEventDispatcher()); + mgr.featuresService = new TestFeaturesService(); + mgr.store = new TestStore(); + mgr.activate(); + mgr.addListener(listener); + } + + @After + public void tearDown() { + mgr.removeListener(listener); + mgr.deactivate(); + } + + private void validate(Application app) { + assertEquals("incorrect name", APP_NAME, app.id().name()); + assertEquals("incorrect version", VER, app.version()); + assertEquals("incorrect origin", ORIGIN, app.origin()); + + assertEquals("incorrect description", DESC, app.description()); + assertEquals("incorrect features URI", FURL, app.featuresRepo().get()); + assertEquals("incorrect features", FEATURES, app.features()); + } + + @Test + public void install() { + InputStream stream = ApplicationArchive.class.getResourceAsStream("app.zip"); + Application app = mgr.install(stream); + validate(app); + assertEquals("incorrect features URI used", app.featuresRepo().get(), + ((TestFeaturesService) mgr.featuresService).uri); + assertEquals("incorrect app count", 1, mgr.getApplications().size()); + assertEquals("incorrect app", app, mgr.getApplication(APP_ID)); + assertEquals("incorrect app state", INSTALLED, mgr.getState(APP_ID)); + } + + @Test + public void uninstall() { + install(); + mgr.uninstall(APP_ID); + assertEquals("incorrect app count", 0, mgr.getApplications().size()); + } + + @Test + public void activate() { + install(); + mgr.activate(APP_ID); + assertEquals("incorrect app state", ACTIVE, mgr.getState(APP_ID)); + } + + @Test + public void deactivate() { + activate(); + mgr.deactivate(APP_ID); + assertEquals("incorrect app state", INSTALLED, mgr.getState(APP_ID)); + } + + + private class TestListener implements ApplicationListener { + private ApplicationEvent event; + + @Override + public void event(ApplicationEvent event) { + this.event = event; + } + } + + private class TestStore extends ApplicationStoreAdapter { + + private Application app; + private ApplicationState state; + + @Override + public Application create(InputStream appDescStream) { + app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, PERMS, + Optional.of(FURL), FEATURES); + state = INSTALLED; + delegate.notify(new ApplicationEvent(APP_INSTALLED, app)); + return app; + } + + @Override + public Set<Application> getApplications() { + return app != null ? ImmutableSet.of(app) : ImmutableSet.of(); + } + + @Override + public Application getApplication(ApplicationId appId) { + return app; + } + + @Override + public void remove(ApplicationId appId) { + delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app)); + app = null; + state = null; + } + + @Override + public ApplicationState getState(ApplicationId appId) { + return state; + } + + @Override + public void activate(ApplicationId appId) { + state = ApplicationState.ACTIVE; + delegate.notify(new ApplicationEvent(APP_ACTIVATED, app)); + } + + @Override + public void deactivate(ApplicationId appId) { + state = INSTALLED; + delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app)); + } + } + + private class TestFeaturesService extends FeaturesServiceAdapter { + private URI uri; + private Set<String> features = new HashSet<>(); + + @Override + public void addRepository(URI uri) throws Exception { + this.uri = uri; + } + + @Override + public void removeRepository(URI uri) throws Exception { + this.uri = null; + } + + @Override + public void installFeature(String name) throws Exception { + features.add(name); + } + + @Override + public void uninstallFeature(String name) throws Exception { + features.remove(name); + } + } + +}
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java new file mode 100644 index 00000000..fc19b0a1 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java @@ -0,0 +1,168 @@ +/* + * Copyright 2015 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.app.impl; + +import org.apache.karaf.features.Feature; +import org.apache.karaf.features.Repository; + +import java.net.URI; +import java.util.EnumSet; +import java.util.Set; + +/** + * Adapter for testing against Apache Karaf feature service. + */ +public class FeaturesServiceAdapter implements org.apache.karaf.features.FeaturesService { + @Override + public void validateRepository(URI uri) throws Exception { + + } + + @Override + public void addRepository(URI uri) throws Exception { + + } + + @Override + public void addRepository(URI uri, boolean install) throws Exception { + + } + + @Override + public void removeRepository(URI uri) throws Exception { + + } + + @Override + public void removeRepository(URI uri, boolean uninstall) throws Exception { + + } + + @Override + public void restoreRepository(URI uri) throws Exception { + + } + + @Override + public Repository[] listRepositories() { + return new Repository[0]; + } + + @Override + public Repository getRepository(String repoName) { + return null; + } + + @Override + public Repository getRepository(URI uri) { + return null; + } + + @Override + public String getRepositoryName(URI uri) { + return null; + } + + @Override + public void installFeature(String name) throws Exception { + + } + + @Override + public void installFeature(String name, EnumSet<Option> options) throws Exception { + + } + + @Override + public void installFeature(String name, String version) throws Exception { + + } + + @Override + public void installFeature(String name, String version, EnumSet<Option> options) throws Exception { + + } + + @Override + public void installFeature(Feature f, EnumSet<Option> options) throws Exception { + + } + + @Override + public void installFeatures(Set<Feature> features, EnumSet<Option> options) throws Exception { + + } + + @Override + public void uninstallFeature(String name, EnumSet<Option> options) throws Exception { + + } + + @Override + public void uninstallFeature(String name) throws Exception { + + } + + @Override + public void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception { + + } + + @Override + public void uninstallFeature(String name, String version) throws Exception { + + } + + @Override + public Feature[] listFeatures() throws Exception { + return new Feature[0]; + } + + @Override + public Feature[] listInstalledFeatures() { + return new Feature[0]; + } + + @Override + public boolean isInstalled(Feature f) { + return false; + } + + @Override + public Feature[] getFeatures(String name, String version) throws Exception { + return new Feature[0]; + } + + @Override + public Feature[] getFeatures(String name) throws Exception { + return new Feature[0]; + } + + @Override + public Feature getFeature(String name, String version) throws Exception { + return null; + } + + @Override + public Feature getFeature(String name) throws Exception { + return null; + } + + @Override + public void refreshRepository(URI uri) throws Exception { + + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java new file mode 100644 index 00000000..66e9c7b4 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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.cfg.impl; + +import com.google.common.collect.ImmutableSet; +import org.junit.Test; +import org.onosproject.cfg.ConfigProperty; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.onosproject.cfg.ConfigProperty.Type.STRING; +import static org.onosproject.cfg.ConfigProperty.defineProperty; +import static org.onosproject.cfg.impl.ConfigPropertyDefinitions.read; +import static org.onosproject.cfg.impl.ConfigPropertyDefinitions.write; + +/** + * Tests of the config property definitions utility. + */ +public class ConfigPropertyDefinitionsTest { + + @Test + public void basics() throws IOException { + Set<ConfigProperty> original = ImmutableSet + .of(defineProperty("foo", STRING, "dingo", "FOO"), + defineProperty("bar", STRING, "bat", "BAR")); + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + write(out, original); + Set<ConfigProperty> read = read(new ByteArrayInputStream(out.toByteArray())); + assertEquals("incorrect defs", original, read); + } + +}
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java new file mode 100644 index 00000000..bf1a1ff3 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2014-2015 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.cluster.impl; + +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ClusterServiceAdapter; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.DefaultControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.mastership.MastershipService; +import org.onosproject.mastership.MastershipStore; +import org.onosproject.mastership.MastershipTermService; +import org.onosproject.net.DeviceId; +import org.onosproject.store.trivial.SimpleMastershipStore; + +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.onosproject.net.MastershipRole.MASTER; +import static org.onosproject.net.MastershipRole.NONE; +import static org.onosproject.net.MastershipRole.STANDBY; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; + +/** + * Test codifying the mastership service contracts. + */ +public class MastershipManagerTest { + + private static final NodeId NID_LOCAL = new NodeId("local"); + private static final NodeId NID_OTHER = new NodeId("foo"); + private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1"); + private static final DeviceId DEV_MASTER = DeviceId.deviceId("of:1"); + private static final DeviceId DEV_OTHER = DeviceId.deviceId("of:2"); + + private MastershipManager mgr; + protected MastershipService service; + + @Before + public void setUp() { + mgr = new MastershipManager(); + service = mgr; + injectEventDispatcher(mgr, new TestEventDispatcher()); + mgr.clusterService = new TestClusterService(); + mgr.store = new TestSimpleMastershipStore(mgr.clusterService); + mgr.activate(); + } + + @After + public void tearDown() { + mgr.deactivate(); + mgr.clusterService = null; + injectEventDispatcher(mgr, null); + mgr.store = null; + } + + @Test + public void setRole() { + mgr.setRole(NID_OTHER, DEV_MASTER, MASTER); + assertEquals("wrong local role:", NONE, mgr.getLocalRole(DEV_MASTER)); + assertEquals("wrong obtained role:", STANDBY, Futures.getUnchecked(mgr.requestRoleFor(DEV_MASTER))); + + //set to master + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + assertEquals("wrong local role:", MASTER, mgr.getLocalRole(DEV_MASTER)); + } + + @Test + public void relinquishMastership() { + //no backups - should just turn to NONE for device. + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + assertEquals("wrong role:", MASTER, mgr.getLocalRole(DEV_MASTER)); + mgr.relinquishMastership(DEV_MASTER); + assertNull("wrong master:", mgr.getMasterFor(DEV_OTHER)); + assertEquals("wrong role:", NONE, mgr.getLocalRole(DEV_MASTER)); + + //not master, nothing should happen + mgr.setRole(NID_LOCAL, DEV_OTHER, NONE); + mgr.relinquishMastership(DEV_OTHER); + assertNull("wrong role:", mgr.getMasterFor(DEV_OTHER)); + + //provide NID_OTHER as backup and relinquish + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_MASTER)); + mgr.setRole(NID_OTHER, DEV_MASTER, STANDBY); + mgr.relinquishMastership(DEV_MASTER); + assertEquals("wrong master:", NID_OTHER, mgr.getMasterFor(DEV_MASTER)); + } + + @Test + public void requestRoleFor() { + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + mgr.setRole(NID_OTHER, DEV_OTHER, MASTER); + + //local should be master for one but standby for other + assertEquals("wrong role:", MASTER, Futures.getUnchecked(mgr.requestRoleFor(DEV_MASTER))); + assertEquals("wrong role:", STANDBY, Futures.getUnchecked(mgr.requestRoleFor(DEV_OTHER))); + } + + @Test + public void getMasterFor() { + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + mgr.setRole(NID_OTHER, DEV_OTHER, MASTER); + assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_MASTER)); + assertEquals("wrong master:", NID_OTHER, mgr.getMasterFor(DEV_OTHER)); + + //have NID_OTHER hand over DEV_OTHER to NID_LOCAL + mgr.setRole(NID_LOCAL, DEV_OTHER, MASTER); + assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_OTHER)); + } + + @Test + public void getDevicesOf() { + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + mgr.setRole(NID_LOCAL, DEV_OTHER, STANDBY); + assertEquals("should be one device:", 1, mgr.getDevicesOf(NID_LOCAL).size()); + //hand both devices to NID_LOCAL + mgr.setRole(NID_LOCAL, DEV_OTHER, MASTER); + assertEquals("should be two devices:", 2, mgr.getDevicesOf(NID_LOCAL).size()); + } + + @Test + public void termService() { + MastershipTermService ts = mgr; + + //term = 1 for both + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + assertEquals("inconsistent term: ", 1, ts.getMastershipTerm(DEV_MASTER).termNumber()); + + //hand devices to NID_LOCAL and back: term = 1 + 2 + mgr.setRole(NID_OTHER, DEV_MASTER, MASTER); + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); + assertEquals("inconsistent terms: ", 3, ts.getMastershipTerm(DEV_MASTER).termNumber()); + } + + private final class TestClusterService extends ClusterServiceAdapter { + + ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST); + + @Override + public ControllerNode getLocalNode() { + return local; + } + + @Override + public Set<ControllerNode> getNodes() { + return Sets.newHashSet(); + } + + } + + private final class TestSimpleMastershipStore extends SimpleMastershipStore + implements MastershipStore { + + public TestSimpleMastershipStore(ClusterService clusterService) { + super.clusterService = clusterService; + } + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java new file mode 100644 index 00000000..383e981a --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 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.core.impl; + +import org.onosproject.core.IdBlock; + +public class DummyIdBlockAllocator implements IdBlockAllocator { + private long blockTop; + private static final long BLOCK_SIZE = 0x1000000L; + + /** + * Returns a block of IDs which are unique and unused. + * Range of IDs is fixed size and is assigned incrementally as this method + * called. + * + * @return an IdBlock containing a set of unique IDs + */ + @Override + public IdBlock allocateUniqueIdBlock() { + synchronized (this) { + long blockHead = blockTop; + long blockTail = blockTop + BLOCK_SIZE; + + IdBlock block = new IdBlock(blockHead, BLOCK_SIZE); + blockTop = blockTail; + + return block; + } + } + + @Override + public IdBlock allocateUniqueIdBlock(long range) { + throw new UnsupportedOperationException("Not supported yet"); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java new file mode 100644 index 00000000..8a4e6185 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 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.core.impl; + +import org.easymock.EasyMock; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.core.IdBlock; + +/** + * Suites of test of {@link org.onosproject.core.impl.BlockAllocatorBasedIdGenerator}. + */ +public class IdBlockAllocatorBasedIdGeneratorTest { + private IdBlockAllocator allocator; + private BlockAllocatorBasedIdGenerator sut; + + @Before + public void setUp() { + allocator = EasyMock.createMock(IdBlockAllocator.class); + + } + + /** + * Tests generated IntentId sequences using two {@link org.onosproject.core.IdBlock blocks}. + */ + @Test + public void testIds() { + EasyMock.expect(allocator.allocateUniqueIdBlock()) + .andReturn(new IdBlock(0, 3)) + .andReturn(new IdBlock(4, 3)); + + EasyMock.replay(allocator); + sut = new BlockAllocatorBasedIdGenerator(allocator); + + Assert.assertThat(sut.getNewId(), Matchers.is(0L)); + Assert.assertThat(sut.getNewId(), Matchers.is(1L)); + Assert.assertThat(sut.getNewId(), Matchers.is(2L)); + + Assert.assertThat(sut.getNewId(), Matchers.is(4L)); + Assert.assertThat(sut.getNewId(), Matchers.is(5L)); + Assert.assertThat(sut.getNewId(), Matchers.is(6L)); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java new file mode 100644 index 00000000..474d1705 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2015 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.core.impl; + +import org.onosproject.core.CoreServiceAdapter; +import org.onosproject.core.IdGenerator; + +public class TestCoreManager extends CoreServiceAdapter { + + @Override + public IdGenerator getIdGenerator(String topic) { + IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator(); + return new BlockAllocatorBasedIdGenerator(idBlockAllocator); + } + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java new file mode 100644 index 00000000..9ba3db59 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014 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.event.impl; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.event.AbstractEvent; +import org.onosproject.event.EventSink; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; + +/** + * Test of the even dispatcher mechanism. + */ +public class CoreEventDispatcherTest { + + private final CoreEventDispatcher dispatcher = new CoreEventDispatcher(); + private final PrickleSink prickleSink = new PrickleSink(); + private final GooSink gooSink = new GooSink(); + + @Before + public void setUp() { + dispatcher.activate(); + dispatcher.addSink(Prickle.class, prickleSink); + dispatcher.addSink(Goo.class, gooSink); + } + + @After + public void tearDown() { + dispatcher.removeSink(Goo.class); + dispatcher.removeSink(Prickle.class); + dispatcher.deactivate(); + } + + @Test + public void post() throws Exception { + prickleSink.latch = new CountDownLatch(1); + dispatcher.post(new Prickle("yo")); + prickleSink.latch.await(100, TimeUnit.MILLISECONDS); + validate(prickleSink, "yo"); + validate(gooSink); + } + + @Test + public void postEventWithBadSink() throws Exception { + gooSink.latch = new CountDownLatch(1); + dispatcher.post(new Goo("boom")); + gooSink.latch.await(100, TimeUnit.MILLISECONDS); + validate(gooSink, "boom"); + validate(prickleSink); + } + + @Test + public void postEventWithNoSink() throws Exception { + dispatcher.post(new Thing("boom")); + validate(gooSink); + validate(prickleSink); + } + + private void validate(Sink sink, String... strings) { + int i = 0; + assertEquals("incorrect event count", strings.length, sink.subjects.size()); + for (String string : strings) { + assertEquals("incorrect event", string, sink.subjects.get(i++)); + } + } + + private enum Type { FOO } + + private static class Thing extends AbstractEvent<Type, String> { + protected Thing(String subject) { + super(Type.FOO, subject); + } + } + + private static class Prickle extends Thing { + protected Prickle(String subject) { + super(subject); + } + } + + private static class Goo extends Thing { + protected Goo(String subject) { + super(subject); + } + } + + private static class Sink { + final List<String> subjects = new ArrayList<>(); + CountDownLatch latch; + + protected void process(String subject) { + subjects.add(subject); + latch.countDown(); + } + } + + private static class PrickleSink extends Sink implements EventSink<Prickle> { + @Override + public void process(Prickle event) { + process(event.subject()); + } + } + + private static class GooSink extends Sink implements EventSink<Goo> { + @Override + public void process(Goo event) { + process(event.subject()); + throw new IllegalStateException("BOOM!"); + } + } + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java new file mode 100644 index 00000000..2d03bfc8 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java @@ -0,0 +1,242 @@ +/* + * Copyright 2015 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.net.config.impl; + +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.junit.TestUtils; +import org.onosproject.event.EventDeliveryServiceAdapter; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigRegistry; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.SubjectFactory; +import org.onosproject.net.NetTestTools; +import org.onosproject.store.config.impl.DistributedNetworkConfigStore; +import org.onosproject.store.service.TestStorageService; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.testing.EqualsTester; + +/** + * Unit tests for network config registry. + */ +public class NetworkConfigManagerTest { + private NetworkConfigManager manager; + private NetworkConfigRegistry registry; + private NetworkConfigService configService; + private DistributedNetworkConfigStore configStore; + + /** + * Config classes for testing. + */ + public class BasicConfig1 extends Config<String> { } + public class BasicConfig2 extends Config<String> { } + + public class MockSubjectFactory extends SubjectFactory<String> { + protected MockSubjectFactory(Class<String> subjectClass, String subjectKey) { + super(subjectClass, subjectKey); + } + + @Override + public String createSubject(String subjectKey) { + return subjectKey + "-subject"; + } + } + + /** + * Config factory classes for testing. + */ + public class MockConfigFactory1 extends ConfigFactory<String, BasicConfig1> { + protected MockConfigFactory1(SubjectFactory<String> subjectFactory, + Class<BasicConfig1> configClass, String configKey) { + super(subjectFactory, configClass, configKey); + } + @Override + public BasicConfig1 createConfig() { + return new BasicConfig1(); + } + } + + public class MockConfigFactory2 extends ConfigFactory<String, BasicConfig2> { + protected MockConfigFactory2(SubjectFactory<String> subjectFactory, + Class<BasicConfig2> configClass, String configKey) { + super(subjectFactory, configClass, configKey); + } + @Override + public BasicConfig2 createConfig() { + return new BasicConfig2(); + } + } + + MockSubjectFactory factory1 = new MockSubjectFactory(String.class, + "key1"); + MockSubjectFactory factory2 = new MockSubjectFactory(String.class, + "key2"); + + MockConfigFactory1 config1Factory = new MockConfigFactory1(factory1, + BasicConfig1.class, "config1"); + MockConfigFactory2 config2Factory = new MockConfigFactory2(factory2, + BasicConfig2.class, "config2"); + + + @Before + public void setUp() throws Exception { + configStore = new DistributedNetworkConfigStore(); + TestUtils.setField(configStore, "storageService", new TestStorageService()); + configStore.activate(); + manager = new NetworkConfigManager(); + manager.store = configStore; + NetTestTools.injectEventDispatcher(manager, new EventDeliveryServiceAdapter()); + manager.activate(); + registry = manager; + configService = manager; + } + + @After + public void tearDown() { + configStore.deactivate(); + manager.deactivate(); + } + + @Test + public void testRegistry() { + assertThat(registry.getConfigFactories(), hasSize(0)); + assertThat(registry.getConfigFactories(String.class), hasSize(0)); + assertThat(registry.getConfigFactory(BasicConfig1.class), nullValue()); + + registry.registerConfigFactory(config1Factory); + registry.registerConfigFactory(config2Factory); + + assertThat(registry.getConfigFactories(), hasSize(2)); + assertThat(registry.getConfigFactories(String.class), hasSize(2)); + + ConfigFactory queried = registry.getConfigFactory(BasicConfig1.class); + assertThat(queried, is(config1Factory)); + + registry.unregisterConfigFactory(queried); + // Factory associations are not removed according to code documentation + assertThat(registry.getConfigFactories(), hasSize(1)); + assertThat(registry.getConfigFactories(String.class), hasSize(1)); + assertThat(registry.getConfigFactory(BasicConfig1.class), nullValue()); + } + + @Test + public void configIdEquals() { + NetworkConfigManager.ConfigIdentifier id1 = + new NetworkConfigManager.ConfigIdentifier("s1", "c1"); + NetworkConfigManager.ConfigIdentifier likeId1 = + new NetworkConfigManager.ConfigIdentifier("s1", "c1"); + NetworkConfigManager.ConfigIdentifier id2 = + new NetworkConfigManager.ConfigIdentifier("s1", "c2"); + NetworkConfigManager.ConfigIdentifier id3 = + new NetworkConfigManager.ConfigIdentifier("s2", "c1"); + + new EqualsTester().addEqualityGroup(id1, likeId1) + .addEqualityGroup(id2) + .addEqualityGroup(id3) + .testEquals(); + } + + @Test + public void configKeyEquals() { + NetworkConfigManager.ConfigKey key1 = + new NetworkConfigManager.ConfigKey(String.class, String.class); + NetworkConfigManager.ConfigKey likeKey1 = + new NetworkConfigManager.ConfigKey(String.class, String.class); + NetworkConfigManager.ConfigKey key2 = + new NetworkConfigManager.ConfigKey(String.class, Integer.class); + NetworkConfigManager.ConfigKey key3 = + new NetworkConfigManager.ConfigKey(Integer.class, String.class); + + new EqualsTester().addEqualityGroup(key1, likeKey1) + .addEqualityGroup(key2) + .addEqualityGroup(key3) + .testEquals(); + } + + /** + * Tests creation, query and removal of a factory. + */ + @Test + public void testAddConfig() { + + assertThat(configService.getSubjectFactory(String.class), nullValue()); + assertThat(configService.getSubjectFactory("key"), nullValue()); + + registry.registerConfigFactory(config1Factory); + registry.registerConfigFactory(config2Factory); + configService.addConfig("configKey", BasicConfig1.class); + + Config newConfig = configService.getConfig("configKey", BasicConfig1.class); + assertThat(newConfig, notNullValue()); + + assertThat(configService.getSubjectFactory(String.class), notNullValue()); + assertThat(configService.getSubjectFactory("key1"), notNullValue()); + + Set<Class> classes = configService.getSubjectClasses(); + assertThat(classes, hasSize(1)); + + Set<String> subjectsForClass = + configService.getSubjects(String.class); + assertThat(subjectsForClass, hasSize(1)); + + Set<String> subjectsForConfig = + configService.getSubjects(String.class, BasicConfig1.class); + assertThat(subjectsForConfig, hasSize(1)); + + Class queriedConfigClass = configService.getConfigClass("key1", "config1"); + assertThat(queriedConfigClass == BasicConfig1.class, is(true)); + + Set<? extends Config> configs = configService.getConfigs("configKey"); + assertThat(configs.size(), is(1)); + configs.forEach(c -> assertThat(c, instanceOf(BasicConfig1.class))); + + configService.removeConfig("configKey", BasicConfig1.class); + Config newConfigAfterRemove = configService.getConfig("configKey", BasicConfig1.class); + assertThat(newConfigAfterRemove, nullValue()); + } + + /** + * Tests creation, query and removal of a factory. + */ + @Test + public void testApplyConfig() { + + assertThat(configService.getSubjectFactory(String.class), nullValue()); + assertThat(configService.getSubjectFactory("key"), nullValue()); + + registry.registerConfigFactory(config1Factory); + registry.registerConfigFactory(config2Factory); + configService.applyConfig("configKey", BasicConfig1.class, new ObjectMapper().createObjectNode()); + + Config newConfig = configService.getConfig("configKey", BasicConfig1.class); + assertThat(newConfig, notNullValue()); + + assertThat(configService.getSubjectFactory(String.class), notNullValue()); + assertThat(configService.getSubjectFactory("key1"), notNullValue()); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java new file mode 100644 index 00000000..8827c558 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2014-2015 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.net.device.impl; + +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.Device.Type.ROADM; +import static org.junit.Assert.assertEquals; + +import java.net.URI; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigApplyDelegate; +import org.onosproject.net.config.basics.BasicDeviceConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +public class BasicDeviceOperatorTest { + + private static final String NAME1 = "of:foo"; + private static final String NAME2 = "of:bar"; + private static final String OWNER = "somebody"; + private static final URI DURI = URI.create(NAME1); + private static final String MFR = "whitebox"; + private static final String HW = "1.1.x"; + private static final String SW = "3.9.1"; + private static final String SN = "43311-12345"; + private static final ChassisId CID = new ChassisId(); + + private static final SparseAnnotations SA = DefaultAnnotations.builder() + .set(AnnotationKeys.DRIVER, NAME2).build(); + + private static final DeviceDescription DEV1 = new DefaultDeviceDescription( + DURI, SWITCH, MFR, HW, SW, SN, CID, SA); + + private final ConfigApplyDelegate delegate = new ConfigApplyDelegate() { + @Override + public void onApply(Config config) { + } + }; + private final ObjectMapper mapper = new ObjectMapper(); + + private static final BasicDeviceConfig SW_BDC = new BasicDeviceConfig(); + private static final BasicDeviceConfig RD_BDC = new BasicDeviceConfig(); + + @Before + public void setUp() { + SW_BDC.init(DeviceId.deviceId(NAME1), NAME1, JsonNodeFactory.instance.objectNode(), mapper, delegate); + SW_BDC.type(SWITCH).driver(NAME1).owner(OWNER); + RD_BDC.init(DeviceId.deviceId(NAME2), NAME2, JsonNodeFactory.instance.objectNode(), mapper, delegate); + RD_BDC.type(ROADM); + } + + @Test + public void testDescOps() { + DeviceDescription desc = BasicDeviceOperator.combine(null, DEV1); + assertEquals(desc, DEV1); + + // override driver name + desc = BasicDeviceOperator.combine(SW_BDC, DEV1); + assertEquals(NAME1, desc.annotations().value(AnnotationKeys.DRIVER)); + + // override Device Type + desc = BasicDeviceOperator.combine(RD_BDC, DEV1); + assertEquals(ROADM, desc.type()); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java new file mode 100644 index 00000000..04f266f0 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java @@ -0,0 +1,331 @@ +/* + * Copyright 2014-2015 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.net.device.impl; + +import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterServiceAdapter; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.DefaultControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.event.Event; +import org.onosproject.net.config.NetworkConfigServiceAdapter; +import org.onosproject.mastership.MastershipServiceAdapter; +import org.onosproject.mastership.MastershipTerm; +import org.onosproject.mastership.MastershipTermService; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.trivial.SimpleDeviceStore; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static org.junit.Assert.*; +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; +import static org.onosproject.net.device.DeviceEvent.Type.*; + +/** + * Test codifying the device service & device provider service contracts. + */ +public class DeviceManagerTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final DeviceId DID1 = deviceId("of:foo"); + private static final DeviceId DID2 = deviceId("of:bar"); + private static final String MFR = "whitebox"; + private static final String HW = "1.1.x"; + private static final String SW1 = "3.8.1"; + private static final String SW2 = "3.9.5"; + private static final String SN = "43311-12345"; + private static final ChassisId CID = new ChassisId(); + + private static final PortNumber P1 = PortNumber.portNumber(1); + private static final PortNumber P2 = PortNumber.portNumber(2); + private static final PortNumber P3 = PortNumber.portNumber(3); + private static final NodeId NID_LOCAL = new NodeId("local"); + private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1"); + + private DeviceManager mgr; + + protected DeviceService service; + protected DeviceAdminService admin; + protected DeviceProviderRegistry registry; + protected DeviceProviderService providerService; + protected TestProvider provider; + protected TestListener listener = new TestListener(); + + @Before + public void setUp() { + mgr = new DeviceManager(); + service = mgr; + admin = mgr; + registry = mgr; + mgr.store = new SimpleDeviceStore(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + TestMastershipManager mastershipManager = new TestMastershipManager(); + mgr.mastershipService = mastershipManager; + mgr.termService = mastershipManager; + mgr.clusterService = new TestClusterService(); + mgr.networkConfigService = new TestNetworkConfigService(); + mgr.activate(); + + + service.addListener(listener); + + provider = new TestProvider(); + providerService = registry.register(provider); + assertTrue("provider should be registered", + registry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + registry.unregister(provider); + assertFalse("provider should not be registered", + registry.getProviders().contains(provider.id())); + service.removeListener(listener); + mgr.deactivate(); + } + + private void connectDevice(DeviceId deviceId, String swVersion) { + DeviceDescription description = + new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR, + HW, swVersion, SN, CID); + providerService.deviceConnected(deviceId, description); + assertNotNull("device should be found", service.getDevice(DID1)); + } + + @Test + public void deviceConnected() { + assertNull("device should not be found", service.getDevice(DID1)); + connectDevice(DID1, SW1); + validateEvents(DEVICE_ADDED); + + Iterator<Device> it = service.getDevices().iterator(); + assertNotNull("one device expected", it.next()); + assertFalse("only one device expected", it.hasNext()); + assertEquals("incorrect device count", 1, service.getDeviceCount()); + assertTrue("device should be available", service.isAvailable(DID1)); + } + + @Test + public void deviceDisconnected() { + connectDevice(DID1, SW1); + connectDevice(DID2, SW1); + validateEvents(DEVICE_ADDED, DEVICE_ADDED); + assertTrue("device should be available", service.isAvailable(DID1)); + + // Disconnect + providerService.deviceDisconnected(DID1); + assertNotNull("device should not be found", service.getDevice(DID1)); + assertFalse("device should not be available", service.isAvailable(DID1)); + validateEvents(DEVICE_AVAILABILITY_CHANGED); + + // Reconnect + connectDevice(DID1, SW1); + validateEvents(DEVICE_AVAILABILITY_CHANGED); + + assertEquals("incorrect device count", 2, service.getDeviceCount()); + } + + @Test + public void deviceUpdated() { + connectDevice(DID1, SW1); + validateEvents(DEVICE_ADDED); + + connectDevice(DID1, SW2); + validateEvents(DEVICE_UPDATED); + } + + @Test + public void getRole() { + connectDevice(DID1, SW1); + assertEquals("incorrect role", MastershipRole.MASTER, service.getRole(DID1)); + } + + @Test + public void updatePorts() { + connectDevice(DID1, SW1); + List<PortDescription> pds = new ArrayList<>(); + pds.add(new DefaultPortDescription(P1, true)); + pds.add(new DefaultPortDescription(P2, true)); + pds.add(new DefaultPortDescription(P3, true)); + providerService.updatePorts(DID1, pds); + validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED, PORT_ADDED); + pds.clear(); + + pds.add(new DefaultPortDescription(P1, false)); + pds.add(new DefaultPortDescription(P3, true)); + providerService.updatePorts(DID1, pds); + validateEvents(PORT_UPDATED, PORT_REMOVED); + } + + @Test + public void updatePortStatus() { + connectDevice(DID1, SW1); + List<PortDescription> pds = new ArrayList<>(); + pds.add(new DefaultPortDescription(P1, true)); + pds.add(new DefaultPortDescription(P2, true)); + providerService.updatePorts(DID1, pds); + validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED); + + providerService.portStatusChanged(DID1, new DefaultPortDescription(P1, false)); + validateEvents(PORT_UPDATED); + providerService.portStatusChanged(DID1, new DefaultPortDescription(P1, false)); + assertTrue("no events expected", listener.events.isEmpty()); + } + + @Test + public void getPorts() { + connectDevice(DID1, SW1); + List<PortDescription> pds = new ArrayList<>(); + pds.add(new DefaultPortDescription(P1, true)); + pds.add(new DefaultPortDescription(P2, true)); + providerService.updatePorts(DID1, pds); + validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED); + assertEquals("wrong port count", 2, service.getPorts(DID1).size()); + + Port port = service.getPort(DID1, P1); + assertEquals("incorrect port", P1, service.getPort(DID1, P1).number()); + assertEquals("incorrect state", true, service.getPort(DID1, P1).isEnabled()); + } + + @Test + public void removeDevice() { + connectDevice(DID1, SW1); + connectDevice(DID2, SW2); + assertEquals("incorrect device count", 2, service.getDeviceCount()); + admin.removeDevice(DID1); + assertNull("device should not be found", service.getDevice(DID1)); + assertNotNull("device should be found", service.getDevice(DID2)); + assertEquals("incorrect device count", 1, service.getDeviceCount()); + + } + + protected void validateEvents(Enum... types) { + int i = 0; + assertEquals("wrong events received", types.length, listener.events.size()); + for (Event event : listener.events) { + assertEquals("incorrect event type", types[i], event.type()); + i++; + } + listener.events.clear(); + } + + + private class TestProvider extends AbstractProvider implements DeviceProvider { + private DeviceId deviceReceived; + private MastershipRole roleReceived; + + public TestProvider() { + super(PID); + } + + @Override + public void triggerProbe(DeviceId deviceId) { + } + + @Override + public void roleChanged(DeviceId device, MastershipRole newRole) { + deviceReceived = device; + roleReceived = newRole; + } + + @Override + public boolean isReachable(DeviceId device) { + return false; + } + } + + private static class TestListener implements DeviceListener { + final List<DeviceEvent> events = new ArrayList<>(); + + @Override + public void event(DeviceEvent event) { + events.add(event); + } + } + + private static class TestMastershipManager + extends MastershipServiceAdapter implements MastershipTermService { + @Override + public MastershipRole getLocalRole(DeviceId deviceId) { + return MastershipRole.MASTER; + } + + @Override + public Set<DeviceId> getDevicesOf(NodeId nodeId) { + return Sets.newHashSet(DID1, DID2); + } + + @Override + public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) { + return CompletableFuture.completedFuture(MastershipRole.MASTER); + } + + @Override + public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) { + return CompletableFuture.completedFuture(null); + } + + @Override + public MastershipTerm getMastershipTerm(DeviceId deviceId) { + // FIXME: just returning something not null + return MastershipTerm.of(NID_LOCAL, 1); + } + } + + // code clone + private final class TestClusterService extends ClusterServiceAdapter { + + ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST); + + @Override + public ControllerNode getLocalNode() { + return local; + } + + } + + private class TestNetworkConfigService extends NetworkConfigServiceAdapter { + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java new file mode 100644 index 00000000..78bc08e0 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java @@ -0,0 +1,80 @@ +package org.onosproject.net.device.impl; + +import org.junit.Before; +import org.junit.Test; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigApplyDelegate; +import org.onosproject.net.config.basics.OpticalPortConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.OduCltPort; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.OduCltPortDescription; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +import static org.junit.Assert.assertEquals; + +public class OpticalPortOperatorTest { + private static final DeviceId DID = DeviceId.deviceId("op-test"); + private static final String TPNAME = "test-port-100"; + private static final String SPNAME = "out-port-200"; + private static final String CFGNAME = "cfg-name"; + + private static final PortNumber NAMED = PortNumber.portNumber(100, TPNAME); + private static final PortNumber UNNAMED = PortNumber.portNumber(101); + private static final ConnectPoint NCP = new ConnectPoint(DID, UNNAMED); + + private static final SparseAnnotations SA = DefaultAnnotations.builder() + .set(AnnotationKeys.STATIC_PORT, SPNAME) + .build(); + + private static final OduCltPortDescription N_DESC = new OduCltPortDescription( + NAMED, true, OduCltPort.SignalType.CLT_100GBE, SA); + private static final OduCltPortDescription FAULTY = new OduCltPortDescription( + null, true, OduCltPort.SignalType.CLT_100GBE); + + private final ConfigApplyDelegate delegate = new MockCfgDelegate(); + private final ObjectMapper mapper = new ObjectMapper(); + + private static final OpticalPortConfig N_OPC = new OpticalPortConfig(); + private static final OpticalPortConfig UNN_OPC = new OpticalPortConfig(); + + @Before + public void setUp() { + N_OPC.init(NCP, TPNAME, JsonNodeFactory.instance.objectNode(), mapper, delegate); + UNN_OPC.init(NCP, TPNAME, JsonNodeFactory.instance.objectNode(), mapper, delegate); + + N_OPC.portName(CFGNAME).portNumberName(101L).portType(Port.Type.ODUCLT).staticLambda(300L); + UNN_OPC.portType(Port.Type.ODUCLT); + } + + @Test(expected = RuntimeException.class) + public void testDescOps() { + // port-null desc + opc with port number name + OduCltPortDescription res = (OduCltPortDescription) OpticalPortOperator.combine(N_OPC, FAULTY); + assertEquals(CFGNAME, res.portNumber().name()); + // full desc + opc with name + assertEquals(TPNAME, N_DESC.portNumber().name()); + res = (OduCltPortDescription) OpticalPortOperator.combine(N_OPC, N_DESC); + long sl = Long.valueOf(res.annotations().value(AnnotationKeys.STATIC_LAMBDA)); + assertEquals(CFGNAME, res.portNumber().name()); + assertEquals(300L, sl); + // port-null desc + opc without port number name - throws RE + res = (OduCltPortDescription) OpticalPortOperator.combine(UNN_OPC, FAULTY); + } + + private class MockCfgDelegate implements ConfigApplyDelegate { + + @Override + public void onApply(@SuppressWarnings("rawtypes") Config config) { + config.apply(); + } + + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java new file mode 100644 index 00000000..319412fe --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java @@ -0,0 +1,514 @@ +/* + * Copyright 2015 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.net.edgeservice.impl; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.event.Event; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.NetTestTools; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.edge.EdgePortEvent; +import org.onosproject.net.edge.EdgePortListener; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketServiceAdapter; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyListener; +import org.onosproject.net.topology.TopologyServiceAdapter; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; +import static org.onosproject.net.device.DeviceEvent.Type.*; +import static org.onosproject.net.edge.EdgePortEvent.Type.EDGE_PORT_ADDED; +import static org.onosproject.net.edge.EdgePortEvent.Type.EDGE_PORT_REMOVED; +import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; +import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; +import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED; + +/** + * Test of the edge port manager. Each device has ports '0' through 'numPorts - 1' + * as specified by the variable 'numPorts'. + */ +public class EdgeManagerTest { + + private EdgeManager mgr; + private int totalPorts = 10; + private boolean alwaysReturnPorts = false; + private final Set<ConnectPoint> infrastructurePorts = Sets.newConcurrentHashSet(); + private List<EdgePortEvent> events = Lists.newArrayList(); + private final Map<DeviceId, Device> devices = Maps.newConcurrentMap(); + private Set<OutboundPacket> packets = Sets.newConcurrentHashSet(); + private final EdgePortListener testListener = new TestListener(events); + private TestTopologyManager testTopologyManager; + + @Before + public void setUp() { + mgr = new EdgeManager(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + testTopologyManager = new TestTopologyManager(infrastructurePorts); + mgr.topologyService = testTopologyManager; + mgr.deviceService = new TestDeviceManager(devices); + mgr.packetService = new TestPacketManager(); + mgr.activate(); + mgr.addListener(testListener); + + } + + + @After + public void tearDown() { + mgr.removeListener(testListener); + mgr.deactivate(); + } + + @Test + public void testBasics() { + //Setup + int numDevices = 20; + int numPorts = 4; + defaultPopulator(numDevices, numPorts); + + assertEquals("Unexpected number of ports", numDevices * numPorts, infrastructurePorts.size()); + + assertFalse("no ports expected", mgr.getEdgePoints().iterator().hasNext()); + + assertFalse("Expected isEdge to return false", + mgr.isEdgePoint(NetTestTools.connectPoint(Integer.toString(1), 1))); + + removeInfraPort(NetTestTools.connectPoint(Integer.toString(1), 1)); + assertTrue("Expected isEdge to return false", + mgr.isEdgePoint(NetTestTools.connectPoint(Integer.toString(1), 1))); + } + + @Test + public void testLinkUpdates() { + //Setup + ConnectPoint testPoint, referencePoint; + + //Testing link removal + List<Event> eventsToAdd = Lists.newArrayList(); + eventsToAdd.add(new LinkEvent(LINK_REMOVED, NetTestTools.link("a", 1, "b", 2))); + TopologyEvent event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + assertTrue("The list contained an unexpected number of events", events.size() == 2); + assertTrue("The first element is of the wrong type.", + events.get(0).type() == EDGE_PORT_ADDED); + assertTrue("The second element is of the wrong type.", + events.get(1).type() == EDGE_PORT_ADDED); + + testPoint = events.get(0).subject(); + referencePoint = NetTestTools.connectPoint("a", 1); + assertTrue("The port numbers of the first element are incorrect", + testPoint.port().toLong() == referencePoint.port().toLong()); + assertTrue("The device id of the first element is incorrect.", + testPoint.deviceId().equals(referencePoint.deviceId())); + + testPoint = events.get(1).subject(); + referencePoint = NetTestTools.connectPoint("b", 2); + assertTrue("The port numbers of the second element are incorrect", + testPoint.port().toLong() == referencePoint.port().toLong()); + assertTrue("The device id of the second element is incorrect.", + testPoint.deviceId().equals(referencePoint.deviceId())); + + //Rebroadcast event to ensure it results in no additional events + testTopologyManager.listener.event(event); + assertTrue("The list contained an unexpected number of events", events.size() == 2); + + //Testing link adding when links to remove exist + eventsToAdd.clear(); + events.clear(); + eventsToAdd.add(new LinkEvent(LINK_ADDED, NetTestTools.link("a", 1, "b", 2))); + event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + assertTrue("The list contained an unexpected number of events", events.size() == 2); + assertTrue("The first element is of the wrong type.", + events.get(0).type() == EDGE_PORT_REMOVED); + assertTrue("The second element is of the wrong type.", + events.get(1).type() == EDGE_PORT_REMOVED); + + testPoint = events.get(0).subject(); + referencePoint = NetTestTools.connectPoint("a", 1); + assertTrue("The port numbers of the first element are incorrect", + testPoint.port().toLong() == referencePoint.port().toLong()); + assertTrue("The device id of the first element is incorrect.", + testPoint.deviceId().equals(referencePoint.deviceId())); + + testPoint = events.get(1).subject(); + referencePoint = NetTestTools.connectPoint("b", 2); + assertTrue("The port numbers of the second element are incorrect", + testPoint.port().toLong() == referencePoint.port().toLong()); + assertTrue("The device id of the second element is incorrect.", + testPoint.deviceId().equals(referencePoint.deviceId())); + + //Apparent duplicate of previous method tests removal when the elements have already been removed + eventsToAdd.clear(); + events.clear(); + eventsToAdd.add(new LinkEvent(LINK_ADDED, NetTestTools.link("a", 1, "b", 2))); + event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + assertTrue("The list should contain no events, the removed elements don't exist.", events.size() == 0); + } + + @Test + public void testDeviceUpdates() { + //Setup + + Device referenceDevice; + TopologyEvent event; + List<Event> eventsToAdd = Lists.newArrayList(); + int numDevices = 10; + int numInfraPorts = 5; + totalPorts = 10; + defaultPopulator(numDevices, numInfraPorts); + + //Test response to device added events + referenceDevice = NetTestTools.device("1"); + eventsToAdd.add(new DeviceEvent(DEVICE_ADDED, referenceDevice, + new DefaultPort(referenceDevice, PortNumber.portNumber(1), true))); + event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + //Check that ports were populated correctly + assertTrue("Unexpected number of new ports added", + mgr.deviceService.getPorts(NetTestTools.did("1")).size() == 10); + + //Check that of the ten ports the half that are infrastructure ports aren't added + assertEquals("Unexpected number of new edge ports added", (totalPorts - numInfraPorts), events.size()); + + for (int index = 0; index < numInfraPorts; index++) { + assertTrue("Unexpected type of event", events.get(index).type() == EDGE_PORT_ADDED); + } + //Names here are irrelevant, the first 5 ports are populated as infrastructure, 6-10 are edge + for (int index = 0; index < events.size(); index++) { + assertEquals("Port added had unexpected port number.", + events.get(index).subject().port(), + NetTestTools.connectPoint("a", index + numInfraPorts + 1).port()); + } + events.clear(); + + //Repost the event to test repeated posts + testTopologyManager.listener.event(event); + assertEquals("The redundant notification should not have created additional notifications.", + 0, events.size()); + //Calculate the size of the returned iterable of edge points. + Iterable<ConnectPoint> pts = mgr.getEdgePoints(); + Iterator pointIterator = pts.iterator(); + int count = 0; + for (; pointIterator.hasNext(); count++) { + pointIterator.next(); + } + assertEquals("Unexpected number of edge points", totalPorts - numInfraPorts, count); + //Testing device removal + events.clear(); + eventsToAdd.clear(); + eventsToAdd.add(new DeviceEvent(DEVICE_REMOVED, referenceDevice, + new DefaultPort(referenceDevice, PortNumber.portNumber(1), true))); + event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + assertEquals("There should be five new events from removal of edge points", + totalPorts - numInfraPorts, events.size()); + for (int index = 0; index < events.size(); index++) { + //Assert that the correct port numbers were removed in the correct order + assertEquals("Port removed had unexpected port number.", + events.get(index).subject().port(), + (NetTestTools.connectPoint("a", index + numInfraPorts + 1).port())); + //Assert that the events are of the correct type + assertEquals("Unexpected type of event", events.get(index).type(), EDGE_PORT_REMOVED); + } + events.clear(); + //Rebroadcast event to check that it triggers no new behavior + testTopologyManager.listener.event(event); + assertEquals("Rebroadcast of removal event should not produce additional events", + 0, events.size()); + + //Testing device status change, changed from unavailable to available + events.clear(); + eventsToAdd.clear(); + //Make sure that the devicemanager shows the device as available. + addDevice(referenceDevice, "1", 5); + devices.put(referenceDevice.id(), referenceDevice); + + eventsToAdd.add(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, referenceDevice)); + event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + //An earlier setup set half of the reference device ports to infrastructure + assertEquals("An unexpected number of events were generated.", totalPorts - numInfraPorts, events.size()); + for (int i = 0; i < 5; i++) { + assertEquals("The event was not of the right type", events.get(i).type(), EDGE_PORT_ADDED); + } + events.clear(); + testTopologyManager.listener.event(event); + assertEquals("No events should have been generated for a set of existing ports.", 0, events.size()); + + //Test removal when state changes when the device becomes unavailable + + //Ensure that the deviceManager shows the device as unavailable + removeDevice(referenceDevice); + /*This variable copies the behavior of the topology by returning ports attached to an unavailable device + //this behavior is necessary for the following event to execute properly, if these statements are removed + no events will be generated since no ports will be provided in getPorts() to EdgeManager. + */ + alwaysReturnPorts = true; + testTopologyManager.listener.event(event); + alwaysReturnPorts = false; + assertEquals("An unexpected number of events were created.", totalPorts - numInfraPorts, events.size()); + for (int i = 0; i < 5; i++) { + EdgePortEvent edgeEvent = events.get(i); + assertEquals("The event is of an unexpected type.", + EdgePortEvent.Type.EDGE_PORT_REMOVED, edgeEvent.type()); + assertEquals("The event pertains to an unexpected port", PortNumber.portNumber(i + numInfraPorts + 1), + edgeEvent.subject().port()); + } + } + + @Test + public void testInternalCache() { + List<Event> eventsToAdd = Lists.newArrayList(); + int numDevices = 10; + //Number of infrastructure ports per device + int numPorts = 5; + //Total ports per device when requesting all devices + totalPorts = 10; + defaultPopulator(numDevices, numPorts); + for (int i = 0; i < numDevices; i++) { + Device newDevice = NetTestTools.device(Integer.toString(i)); + devices.put(newDevice.id(), newDevice); + eventsToAdd.add(new DeviceEvent(DEVICE_ADDED, newDevice)); + } + TopologyEvent event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + //Check all ports have correct designations + ConnectPoint testPoint; + for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) { + for (int portNum = 1; portNum <= totalPorts; portNum++) { + testPoint = NetTestTools.connectPoint(Integer.toString(deviceNum), portNum); + if (portNum <= numPorts) { + assertFalse("This should not be an edge point", mgr.isEdgePoint(testPoint)); + } else { + assertTrue("This should be an edge point", mgr.isEdgePoint(testPoint)); + } + } + } + int count = 0; + for (ConnectPoint ignored : mgr.getEdgePoints()) { + count++; + } + assertEquals("There are an unexpeceted number of edge points returned.", + (totalPorts - numPorts) * numDevices, count); + for (int deviceNumber = 0; deviceNumber < numDevices; deviceNumber++) { + count = 0; + for (ConnectPoint ignored : mgr.getEdgePoints(NetTestTools.did("1"))) { + count++; + } + assertEquals("This element has an unexpected number of edge points.", (totalPorts - numPorts), count); + } + } + + + @Test + public void testEmit() { + byte[] arr = new byte[10]; + Device referenceDevice; + TopologyEvent event; + List<Event> eventsToAdd = Lists.newArrayList(); + int numDevices = 10; + int numInfraPorts = 5; + totalPorts = 10; + defaultPopulator(numDevices, numInfraPorts); + for (byte byteIndex = 0; byteIndex < arr.length; byteIndex++) { + arr[byteIndex] = byteIndex; + } + for (int i = 0; i < numDevices; i++) { + referenceDevice = NetTestTools.device(Integer.toString(i)); + eventsToAdd.add(new DeviceEvent(DEVICE_ADDED, referenceDevice, + new DefaultPort(referenceDevice, PortNumber.portNumber(1), true))); + } + event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd); + testTopologyManager.listener.event(event); + + mgr.emitPacket(ByteBuffer.wrap(arr), Optional.<TrafficTreatment>empty()); + + assertEquals("There were an unexpected number of emitted packets", + (totalPorts - numInfraPorts) * numDevices, packets.size()); + Iterator<OutboundPacket> packetIter = packets.iterator(); + OutboundPacket packet; + while (packetIter.hasNext()) { + packet = packetIter.next(); + assertEquals("The packet had an incorrect payload.", arr, packet.data().array()); + } + //Start testing emission to a specific device + packets.clear(); + mgr.emitPacket(NetTestTools.did(Integer.toString(1)), ByteBuffer.wrap(arr), Optional.<TrafficTreatment>empty()); + + assertEquals("Unexpected number of outbound packets were emitted.", + totalPorts - numInfraPorts, packets.size()); + packetIter = packets.iterator(); + while (packetIter.hasNext()) { + packet = packetIter.next(); + assertEquals("The packet had an incorrect payload", arr, packet.data().array()); + } + } + + + /** + * @param numDevices the number of devices to populate. + * @param numInfraPorts the number of ports to be set as infrastructure on each device, numbered base 0, ports 0 + * through numInfraPorts - 1 + */ + private void defaultPopulator(int numDevices, int numInfraPorts) { + for (int device = 0; device < numDevices; device++) { + String str = Integer.toString(device); + Device deviceToAdd = NetTestTools.device(str); + devices.put(deviceToAdd.id(), deviceToAdd); + for (int port = 1; port <= numInfraPorts; port++) { + infrastructurePorts.add(NetTestTools.connectPoint(str, port)); + } + } + } + + /** + * Adds the specified device with the specified number of edge ports so long as it is less than the total ports. + * + * @param device The device to be added + * @param deviceName The name given to generate the devices DID + * @param numInfraPorts The number of ports to be added numbered 1 ... numInfraPorts + */ + private void addDevice(Device device, String deviceName, int numInfraPorts) { + if (!devices.keySet().contains(device.id())) { + devices.put(device.id(), device); + for (int i = 1; i <= numInfraPorts && i <= totalPorts; i++) { + infrastructurePorts.add(NetTestTools.connectPoint(deviceName, i)); + } + } + } + + private void removeDevice(Device device) { + devices.remove(device.id()); + } + + private void removeInfraPort(ConnectPoint port) { + infrastructurePorts.remove(port); + } + + private class TestTopologyManager extends TopologyServiceAdapter { + private TopologyListener listener; + private Set<ConnectPoint> infrastructurePorts; + + public TestTopologyManager(Set<ConnectPoint> infrastructurePorts) { + this.infrastructurePorts = infrastructurePorts; + } + + @Override + public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { + return infrastructurePorts.contains(connectPoint); + } + + @Override + public void addListener(TopologyListener listener) { + this.listener = listener; + } + + @Override + public void removeListener(TopologyListener listener) { + this.listener = null; + } + } + + private class TestDeviceManager extends DeviceServiceAdapter { + + private Map<DeviceId, Device> devices; + + public TestDeviceManager(Map<DeviceId, Device> devices) { + this.devices = devices; + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + for (DeviceId id : devices.keySet()) { + if (id.equals(deviceId)) { + return true; + } + } + return false; + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + List<Port> ports = new ArrayList<>(); + Device device = devices.get(deviceId); + if (device == null && !alwaysReturnPorts) { + return ports; + } + for (int portNum = 1; portNum <= totalPorts; portNum++) { + //String is generated using 'of:' + the passed name, this creates a + ports.add(new DefaultPort(device, PortNumber.portNumber(portNum), true)); + } + return ports; + } + + @Override + public Iterable<Device> getAvailableDevices() { + return devices.values(); + } + } + + private class TestPacketManager extends PacketServiceAdapter { + @Override + public void emit(OutboundPacket packet) { + packets.add(packet); + } + } + + private class TestListener implements EdgePortListener { + private List<EdgePortEvent> events; + + public TestListener(List<EdgePortEvent> events) { + this.events = events; + } + + @Override + public void event(EdgePortEvent event) { + events.add(event); + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java new file mode 100644 index 00000000..7ef8762c --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java @@ -0,0 +1,640 @@ +/* + * Copyright 2014-2015 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.net.flow.impl; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreServiceAdapter; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.core.IdGenerator; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.Device; +import org.onosproject.net.Device.Type; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.flow.CompletedBatchOperation; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowEntry.FlowEntryState; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleBatchOperation; +import org.onosproject.net.flow.FlowRuleEvent; +import org.onosproject.net.flow.FlowRuleListener; +import org.onosproject.net.flow.FlowRuleProvider; +import org.onosproject.net.flow.FlowRuleProviderRegistry; +import org.onosproject.net.flow.FlowRuleProviderService; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.StoredFlowEntry; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.Instructions.MetadataInstruction; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.trivial.SimpleFlowRuleStore; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADDED; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_UPDATED; + +/** + * Test codifying the flow rule service & flow rule provider service contracts. + */ +public class FlowRuleManagerTest { + + + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final DeviceId DID = DeviceId.deviceId("of:001"); + private static final int TIMEOUT = 10; + private static final Device DEV = new DefaultDevice( + PID, DID, Type.SWITCH, "", "", "", "", null); + + private FlowRuleManager mgr; + + protected FlowRuleService service; + protected FlowRuleProviderRegistry registry; + protected FlowRuleProviderService providerService; + protected TestProvider provider; + protected TestListener listener = new TestListener(); + private ApplicationId appId; + + + @Before + public void setUp() { + mgr = new FlowRuleManager(); + mgr.store = new SimpleFlowRuleStore(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + mgr.deviceService = new TestDeviceService(); + mgr.coreService = new TestCoreService(); + mgr.operationsService = MoreExecutors.newDirectExecutorService(); + mgr.deviceInstallers = MoreExecutors.newDirectExecutorService(); + mgr.cfgService = new ComponentConfigAdapter(); + service = mgr; + registry = mgr; + + mgr.activate(null); + mgr.addListener(listener); + provider = new TestProvider(PID); + providerService = registry.register(provider); + appId = new TestApplicationId(0, "FlowRuleManagerTest"); + assertTrue("provider should be registered", + registry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + registry.unregister(provider); + assertFalse("provider should not be registered", + registry.getProviders().contains(provider.id())); + service.removeListener(listener); + mgr.deactivate(); + injectEventDispatcher(mgr, null); + mgr.deviceService = null; + } + + private FlowRule flowRule(int tsval, int trval) { + TestSelector ts = new TestSelector(tsval); + TestTreatment tr = new TestTreatment(trval); + return DefaultFlowRule.builder() + .forDevice(DID) + .withSelector(ts) + .withTreatment(tr) + .withPriority(10) + .fromApp(appId) + .makeTemporary(TIMEOUT) + .build(); + } + + + private FlowRule addFlowRule(int hval) { + FlowRule rule = flowRule(hval, hval); + service.applyFlowRules(rule); + + assertNotNull("rule should be found", service.getFlowEntries(DID)); + return rule; + } + + private void validateEvents(FlowRuleEvent.Type... events) { + if (events == null) { + assertTrue("events generated", listener.events.isEmpty()); + } + + int i = 0; + System.err.println("events :" + listener.events); + for (FlowRuleEvent e : listener.events) { + assertEquals("unexpected event", events[i], e.type()); + i++; + } + + assertEquals("mispredicted number of events", + events.length, listener.events.size()); + + listener.events.clear(); + } + + private int flowCount() { + return Sets.newHashSet(service.getFlowEntries(DID)).size(); + } + + @Test + public void getFlowEntries() { + assertTrue("store should be empty", + Sets.newHashSet(service.getFlowEntries(DID)).isEmpty()); + FlowRule f1 = addFlowRule(1); + FlowRule f2 = addFlowRule(2); + + FlowEntry fe1 = new DefaultFlowEntry(f1); + FlowEntry fe2 = new DefaultFlowEntry(f2); + assertEquals("2 rules should exist", 2, flowCount()); + + providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2)); + validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, + RULE_ADDED, RULE_ADDED); + + addFlowRule(1); + System.err.println("events :" + listener.events); + assertEquals("should still be 2 rules", 2, flowCount()); + + providerService.pushFlowMetrics(DID, ImmutableList.of(fe1)); + validateEvents(RULE_UPDATED); + } + + private boolean validateState(Map<FlowRule, FlowEntryState> expected) { + Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected); + Iterable<FlowEntry> rules = service.getFlowEntries(DID); + for (FlowEntry f : rules) { + assertTrue("Unexpected FlowRule " + f, expectedToCheck.containsKey(f)); + assertEquals("FlowEntry" + f, expectedToCheck.get(f), f.state()); + expectedToCheck.remove(f); + } + assertEquals(Collections.emptySet(), expectedToCheck.entrySet()); + return true; + } + + @Test + public void applyFlowRules() { + + FlowRule r1 = flowRule(1, 1); + FlowRule r2 = flowRule(2, 2); + FlowRule r3 = flowRule(3, 3); + + assertTrue("store should be empty", + Sets.newHashSet(service.getFlowEntries(DID)).isEmpty()); + mgr.applyFlowRules(r1, r2, r3); + assertEquals("3 rules should exist", 3, flowCount()); + assertTrue("Entries should be pending add.", + validateState(ImmutableMap.of( + r1, FlowEntryState.PENDING_ADD, + r2, FlowEntryState.PENDING_ADD, + r3, FlowEntryState.PENDING_ADD))); + } + + @Test + public void removeFlowRules() { + FlowRule f1 = addFlowRule(1); + FlowRule f2 = addFlowRule(2); + FlowRule f3 = addFlowRule(3); + assertEquals("3 rules should exist", 3, flowCount()); + + FlowEntry fe1 = new DefaultFlowEntry(f1); + FlowEntry fe2 = new DefaultFlowEntry(f2); + FlowEntry fe3 = new DefaultFlowEntry(f3); + providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2, fe3)); + validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, + RULE_ADDED, RULE_ADDED, RULE_ADDED); + + mgr.removeFlowRules(f1, f2); + //removing from north, so no events generated + validateEvents(RULE_REMOVE_REQUESTED, RULE_REMOVE_REQUESTED); + assertEquals("3 rule should exist", 3, flowCount()); + assertTrue("Entries should be pending remove.", + validateState(ImmutableMap.of( + f1, FlowEntryState.PENDING_REMOVE, + f2, FlowEntryState.PENDING_REMOVE, + f3, FlowEntryState.ADDED))); + + mgr.removeFlowRules(f1); + assertEquals("3 rule should still exist", 3, flowCount()); + } + + @Test + public void flowRemoved() { + + FlowRule f1 = addFlowRule(1); + FlowRule f2 = addFlowRule(2); + StoredFlowEntry fe1 = new DefaultFlowEntry(f1); + FlowEntry fe2 = new DefaultFlowEntry(f2); + + + providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2)); + service.removeFlowRules(f1); + + fe1.setState(FlowEntryState.REMOVED); + + + + providerService.flowRemoved(fe1); + + + validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED, + RULE_ADDED, RULE_REMOVE_REQUESTED, RULE_REMOVED); + + providerService.flowRemoved(fe1); + validateEvents(); + + FlowRule f3 = flowRule(3, 3); + FlowEntry fe3 = new DefaultFlowEntry(f3); + service.applyFlowRules(f3); + + providerService.pushFlowMetrics(DID, Collections.singletonList(fe3)); + validateEvents(RULE_ADD_REQUESTED, RULE_ADDED); + + providerService.flowRemoved(fe3); + validateEvents(); + + } + + @Test + public void flowMetrics() { + FlowRule f1 = flowRule(1, 1); + FlowRule f2 = flowRule(2, 2); + FlowRule f3 = flowRule(3, 3); + + mgr.applyFlowRules(f1, f2, f3); + + FlowEntry fe1 = new DefaultFlowEntry(f1); + FlowEntry fe2 = new DefaultFlowEntry(f2); + + //FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED); + //FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED); + + providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2)); + + assertTrue("Entries should be added.", + validateState(ImmutableMap.of( + f1, FlowEntryState.ADDED, + f2, FlowEntryState.ADDED, + f3, FlowEntryState.PENDING_ADD))); + + validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, + RULE_ADDED, RULE_ADDED); + } + + @Test + public void extraneousFlow() { + FlowRule f1 = flowRule(1, 1); + FlowRule f2 = flowRule(2, 2); + FlowRule f3 = flowRule(3, 3); + mgr.applyFlowRules(f1, f2); + +// FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED); +// FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED); +// FlowRule updatedF3 = flowRule(f3, FlowRuleState.ADDED); + FlowEntry fe1 = new DefaultFlowEntry(f1); + FlowEntry fe2 = new DefaultFlowEntry(f2); + FlowEntry fe3 = new DefaultFlowEntry(f3); + + + providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2, fe3)); + + validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED, RULE_ADDED); + + } + + /* + * Tests whether a rule that was marked for removal but no flowRemoved was received + * is indeed removed at the next stats update. + */ + @Test + public void flowMissingRemove() { + FlowRule f1 = flowRule(1, 1); + FlowRule f2 = flowRule(2, 2); + FlowRule f3 = flowRule(3, 3); + +// FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED); +// FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED); + + FlowEntry fe1 = new DefaultFlowEntry(f1); + FlowEntry fe2 = new DefaultFlowEntry(f2); + mgr.applyFlowRules(f1, f2, f3); + + mgr.removeFlowRules(f3); + + providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2)); + + validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, + RULE_REMOVE_REQUESTED, RULE_ADDED, RULE_ADDED, RULE_REMOVED); + + } + + @Test + public void getByAppId() { + FlowRule f1 = flowRule(1, 1); + FlowRule f2 = flowRule(2, 2); + mgr.applyFlowRules(f1, f2); + + assertTrue("should have two rules", + Lists.newLinkedList(mgr.getFlowRulesById(appId)).size() == 2); + } + + @Test + public void removeByAppId() { + FlowRule f1 = flowRule(1, 1); + FlowRule f2 = flowRule(2, 2); + mgr.applyFlowRules(f1, f2); + + + mgr.removeFlowRulesById(appId); + + //only check that we are in pending remove. Events and actual remove state will + // be set by flowRemoved call. + validateState(ImmutableMap.of( + f1, FlowEntryState.PENDING_REMOVE, + f2, FlowEntryState.PENDING_REMOVE)); + } + + private static class TestListener implements FlowRuleListener { + final List<FlowRuleEvent> events = new ArrayList<>(); + + @Override + public void event(FlowRuleEvent event) { + events.add(event); + } + } + + private static class TestDeviceService extends DeviceServiceAdapter { + + @Override + public int getDeviceCount() { + return 1; + } + + @Override + public Iterable<Device> getDevices() { + return Collections.singletonList(DEV); + } + + @Override + public Device getDevice(DeviceId deviceId) { + return DEV; + } + + @Override + public MastershipRole getRole(DeviceId deviceId) { + return null; + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + return null; + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + return null; + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + return false; + } + + @Override + public void addListener(DeviceListener listener) { + } + + @Override + public void removeListener(DeviceListener listener) { + } + + } + + private class TestProvider extends AbstractProvider implements FlowRuleProvider { + + protected TestProvider(ProviderId id) { + super(PID); + } + + @Override + public void applyFlowRule(FlowRule... flowRules) { + } + + @Override + public void removeFlowRule(FlowRule... flowRules) { + } + + @Override + public void removeRulesById(ApplicationId id, FlowRule... flowRules) { + } + + @Override + public void executeBatch(FlowRuleBatchOperation batch) { + // TODO: need to call batchOperationComplete + } + + private class TestInstallationFuture + implements ListenableFuture<CompletedBatchOperation> { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public CompletedBatchOperation get() + throws InterruptedException, ExecutionException { + return new CompletedBatchOperation(true, Collections.<FlowRule>emptySet(), null); + } + + @Override + public CompletedBatchOperation get(long timeout, TimeUnit unit) + throws InterruptedException, + ExecutionException, TimeoutException { + return new CompletedBatchOperation(true, Collections.<FlowRule>emptySet(), null); + } + + @Override + public void addListener(Runnable task, Executor executor) { + if (isDone()) { + executor.execute(task); + } + } + } + + } + + private class TestSelector implements TrafficSelector { + + //for controlling hashcode uniqueness; + private final int testval; + + public TestSelector(int val) { + testval = val; + } + + @Override + public Set<Criterion> criteria() { + return null; + } + + @Override + public Criterion getCriterion( + org.onosproject.net.flow.criteria.Criterion.Type type) { + return null; + } + + @Override + public int hashCode() { + return testval; + } + + @Override + public boolean equals(Object o) { + if (o instanceof TestSelector) { + return this.testval == ((TestSelector) o).testval; + } + return false; + } + + } + + private class TestTreatment implements TrafficTreatment { + + //for controlling hashcode uniqueness; + private final int testval; + + public TestTreatment(int val) { + testval = val; + } + + @Override + public List<Instruction> deferred() { + return null; + } + + @Override + public List<Instruction> immediate() { + return null; + } + + @Override + public List<Instruction> allInstructions() { + return null; + } + + @Override + public Instructions.TableTypeTransition tableTransition() { + return null; + } + + @Override + public boolean clearedDeferred() { + return false; + } + + @Override + public int hashCode() { + return testval; + } + + @Override + public boolean equals(Object o) { + if (o instanceof TestTreatment) { + return this.testval == ((TestTreatment) o).testval; + } + return false; + } + + @Override + public MetadataInstruction writeMetadata() { + return null; + } + + @Override + public Instructions.MeterInstruction metered() { + return null; + } + + } + + public class TestApplicationId extends DefaultApplicationId { + public TestApplicationId(int id, String name) { + super(id, name); + } + } + + private class TestCoreService extends CoreServiceAdapter { + + @Override + public IdGenerator getIdGenerator(String topic) { + return new IdGenerator() { + private AtomicLong counter = new AtomicLong(0); + @Override + public long getNewId() { + return counter.getAndIncrement(); + } + }; + } + } + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java new file mode 100644 index 00000000..1e898d37 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java @@ -0,0 +1,603 @@ +/* + * Copyright 2014-2015 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.net.flowobjective.impl; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Test FlowObjectiveCompositionTree. + */ +@Ignore +public class FlowObjectiveCompositionTreeTest { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Before + public void setUp() {} + + @After + public void tearDown() {} + + /*@Test + public void testParallelComposition() { + FlowObjectiveCompositionTree policyTree = FlowObjectiveCompositionUtil.parsePolicyString("31+32"); + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPSrc(IpPrefix.valueOf("1.0.0.0/24")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(0) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.1/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(1)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.2/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(2)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(0) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Parallel ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.3/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Parallel ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.3/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .remove(); + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPSrc(IpPrefix.valueOf("1.0.0.0/24")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .remove(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(0) + .withSelector(selector) + .withTreatment(treatment) + .remove(); + + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Parallel ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + } + + @Test + public void testSequentialComposition() { + FlowObjectiveCompositionTree policyTree = FlowObjectiveCompositionUtil.parsePolicyString("31>32"); + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPSrc(IpPrefix.valueOf("0.0.0.0/2")) + .matchIPDst(IpPrefix.valueOf("3.0.0.0/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setIpDst(IpAddress.valueOf("2.0.0.1")) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(3) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("3.0.0.0/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setIpDst(IpAddress.valueOf("2.0.0.2")) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(0) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.1/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(1)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.2/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(2)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(0) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Sequential ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.3/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPSrc(IpPrefix.valueOf("0.0.0.0/1")) + .matchIPDst(IpPrefix.valueOf("3.0.0.0/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setIpDst(IpAddress.valueOf("2.0.0.3")) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(3) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Sequential ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.3/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .remove(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPSrc(IpPrefix.valueOf("0.0.0.0/1")) + .matchIPDst(IpPrefix.valueOf("3.0.0.0/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setIpDst(IpAddress.valueOf("2.0.0.3")) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(3) + .withSelector(selector) + .withTreatment(treatment) + .remove(); + + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Sequential ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + } + + @Test + public void testOverrideComposition() { + FlowObjectiveCompositionTree policyTree = FlowObjectiveCompositionUtil.parsePolicyString("31/32"); + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPSrc(IpPrefix.valueOf("1.0.0.0/32")) + .matchIPDst(IpPrefix.valueOf("2.0.0.1/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(31, "a")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.1/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(1)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.2/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(2)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + helper(policyTree, forwardingObjective); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(0) + .withSelector(selector) + .withTreatment(treatment) + .add(); + + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Override ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.3/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .add(); + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Override ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + + { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPDst(IpPrefix.valueOf("2.0.0.3/32")) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(3)) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .fromApp(new DefaultApplicationId(32, "b")) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withPriority(1) + .withSelector(selector) + .withTreatment(treatment) + .remove(); + helper(policyTree, forwardingObjective); + } + + System.out.println("---------- Override ----------"); + for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) { + System.out.println(forwardingObjectiveToString(fo)); + } + } + + private void helper(FlowObjectiveCompositionTree policyTree, ForwardingObjective forwardingObjective) { + log.info("before composition"); + log.info("\t{}", forwardingObjectiveToString(forwardingObjective)); + List<ForwardingObjective> forwardingObjectives + = policyTree.updateForward(forwardingObjective); + log.info("after composition"); + for (ForwardingObjective fo : forwardingObjectives) { + log.info("\t{}", forwardingObjectiveToString(fo)); + } + }*/ +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java new file mode 100644 index 00000000..ae7cc874 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java @@ -0,0 +1,536 @@ +/* + * Copyright 2015 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.net.group.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.core.GroupId; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.impl.DeviceManager; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.DefaultGroup; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupOperations; +import org.onosproject.net.group.GroupProvider; +import org.onosproject.net.group.GroupProviderRegistry; +import org.onosproject.net.group.GroupProviderService; +import org.onosproject.net.group.GroupService; +import org.onosproject.net.group.StoredGroupEntry; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.trivial.SimpleGroupStore; + +import com.google.common.collect.Iterables; + +/** + * Test codifying the group service & group provider service contracts. + */ +public class GroupManagerTest { + + private static final ProviderId PID = new ProviderId("of", "groupfoo"); + private static final DeviceId DID = DeviceId.deviceId("of:001"); + + private GroupManager mgr; + private GroupService groupService; + private GroupProviderRegistry providerRegistry; + private TestGroupListener internalListener = new TestGroupListener(); + private GroupListener listener = internalListener; + private TestGroupProvider internalProvider; + private GroupProvider provider; + private GroupProviderService providerService; + private ApplicationId appId; + + @Before + public void setUp() { + mgr = new GroupManager(); + groupService = mgr; + mgr.deviceService = new DeviceManager(); + mgr.store = new SimpleGroupStore(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + providerRegistry = mgr; + + mgr.activate(); + mgr.addListener(listener); + + internalProvider = new TestGroupProvider(PID); + provider = internalProvider; + providerService = providerRegistry.register(provider); + appId = new DefaultApplicationId(2, "org.groupmanager.test"); + assertTrue("provider should be registered", + providerRegistry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + providerRegistry.unregister(provider); + assertFalse("provider should not be registered", + providerRegistry.getProviders().contains(provider.id())); + mgr.removeListener(listener); + mgr.deactivate(); + injectEventDispatcher(mgr, null); + } + + /** + * Tests group service north bound and south bound interfaces. + * The following operations are tested: + * a)Tests group creation before the device group AUDIT completes + * b)Tests initial device group AUDIT process + * c)Tests deletion process of any extraneous groups + * d)Tests execution of any pending group creation requests + * after the device group AUDIT completes + * e)Tests re-apply process of any missing groups + * f)Tests event notifications after receiving confirmation for + * any operations from data plane + * g)Tests group bucket modifications (additions and deletions) + * h)Tests group deletion + */ + @Test + public void testGroupService() { + // Test Group creation before AUDIT process + testGroupCreationBeforeAudit(); + + // Test initial group audit process + testInitialAuditWithPendingGroupRequests(); + + // Test audit with extraneous and missing groups + testAuditWithExtraneousMissingGroups(); + + // Test audit with confirmed groups + testAuditWithConfirmedGroups(); + + // Test group add bucket operations + testAddBuckets(); + + // Test group remove bucket operations + testRemoveBuckets(); + + // Test group remove operations + testRemoveGroup(); + } + + // Test Group creation before AUDIT process + private void testGroupCreationBeforeAudit() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes()); + List<GroupBucket> buckets = new ArrayList<>(); + List<PortNumber> outPorts = new ArrayList<>(); + outPorts.addAll(Arrays.asList(ports1)); + outPorts.addAll(Arrays.asList(ports2)); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + GroupDescription newGroupDesc = new DefaultGroupDescription(DID, + Group.Type.SELECT, + groupBuckets, + key, + null, + appId); + groupService.addGroup(newGroupDesc); + internalProvider.validate(DID, null); + assertEquals(null, groupService.getGroup(DID, key)); + assertEquals(0, Iterables.size(groupService.getGroups(DID, appId))); + } + + // Test initial AUDIT process with pending group requests + private void testInitialAuditWithPendingGroupRequests() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + GroupId gId1 = new DefaultGroupId(1); + Group group1 = createSouthboundGroupEntry(gId1, + Arrays.asList(ports1), + 0); + GroupId gId2 = new DefaultGroupId(2); + // Non zero reference count will make the group manager to queue + // the extraneous groups until reference count is zero. + Group group2 = createSouthboundGroupEntry(gId2, + Arrays.asList(ports2), + 2); + List<Group> groupEntries = Arrays.asList(group1, group2); + providerService.pushGroupMetrics(DID, groupEntries); + // First group metrics would trigger the device audit completion + // post which all pending group requests are also executed. + GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes()); + Group createdGroup = groupService.getGroup(DID, key); + int createdGroupId = createdGroup.id().id(); + assertNotEquals(gId1.id(), createdGroupId); + assertNotEquals(gId2.id(), createdGroupId); + + List<GroupOperation> expectedGroupOps = Arrays.asList( + GroupOperation.createDeleteGroupOperation(gId1, + Group.Type.SELECT), + GroupOperation.createAddGroupOperation( + createdGroup.id(), + Group.Type.SELECT, + createdGroup.buckets())); + internalProvider.validate(DID, expectedGroupOps); + } + + // Test AUDIT process with extraneous groups and missing groups + private void testAuditWithExtraneousMissingGroups() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + GroupId gId1 = new DefaultGroupId(1); + Group group1 = createSouthboundGroupEntry(gId1, + Arrays.asList(ports1), + 0); + GroupId gId2 = new DefaultGroupId(2); + Group group2 = createSouthboundGroupEntry(gId2, + Arrays.asList(ports2), + 0); + List<Group> groupEntries = Arrays.asList(group1, group2); + providerService.pushGroupMetrics(DID, groupEntries); + GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes()); + Group createdGroup = groupService.getGroup(DID, key); + List<GroupOperation> expectedGroupOps = Arrays.asList( + GroupOperation.createDeleteGroupOperation(gId1, + Group.Type.SELECT), + GroupOperation.createDeleteGroupOperation(gId2, + Group.Type.SELECT), + GroupOperation.createAddGroupOperation(createdGroup.id(), + Group.Type.SELECT, + createdGroup.buckets())); + internalProvider.validate(DID, expectedGroupOps); + } + + // Test AUDIT with confirmed groups + private void testAuditWithConfirmedGroups() { + GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes()); + Group createdGroup = groupService.getGroup(DID, key); + createdGroup = new DefaultGroup(createdGroup.id(), + DID, + Group.Type.SELECT, + createdGroup.buckets()); + List<Group> groupEntries = Collections.singletonList(createdGroup); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_ADDED)); + } + + // Test group add bucket operations + private void testAddBuckets() { + GroupKey addKey = new DefaultGroupKey("group1AddBuckets".getBytes()); + + GroupKey prevKey = new DefaultGroupKey("group1BeforeAudit".getBytes()); + Group createdGroup = groupService.getGroup(DID, prevKey); + List<GroupBucket> buckets = new ArrayList<>(); + buckets.addAll(createdGroup.buckets().buckets()); + + PortNumber[] addPorts = {PortNumber.portNumber(51), + PortNumber.portNumber(52)}; + List<PortNumber> outPorts; + outPorts = new ArrayList<PortNumber>(); + outPorts.addAll(Arrays.asList(addPorts)); + List<GroupBucket> addBuckets; + addBuckets = new ArrayList<GroupBucket>(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + addBuckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupAddBuckets = new GroupBuckets(addBuckets); + groupService.addBucketsToGroup(DID, + prevKey, + groupAddBuckets, + addKey, + appId); + GroupBuckets updatedBuckets = new GroupBuckets(buckets); + List<GroupOperation> expectedGroupOps = Collections.singletonList( + GroupOperation.createModifyGroupOperation(createdGroup.id(), + Group.Type.SELECT, + updatedBuckets)); + internalProvider.validate(DID, expectedGroupOps); + Group existingGroup = groupService.getGroup(DID, addKey); + List<Group> groupEntries = Collections.singletonList(existingGroup); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATED)); + } + + // Test group remove bucket operations + private void testRemoveBuckets() { + GroupKey removeKey = new DefaultGroupKey("group1RemoveBuckets".getBytes()); + + GroupKey prevKey = new DefaultGroupKey("group1AddBuckets".getBytes()); + Group createdGroup = groupService.getGroup(DID, prevKey); + List<GroupBucket> buckets = new ArrayList<>(); + buckets.addAll(createdGroup.buckets().buckets()); + + PortNumber[] removePorts = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + List<PortNumber> outPorts = new ArrayList<>(); + outPorts.addAll(Arrays.asList(removePorts)); + List<GroupBucket> removeBuckets = new ArrayList<>(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + removeBuckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + buckets.remove(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupRemoveBuckets = new GroupBuckets(removeBuckets); + groupService.removeBucketsFromGroup(DID, + prevKey, + groupRemoveBuckets, + removeKey, + appId); + GroupBuckets updatedBuckets = new GroupBuckets(buckets); + List<GroupOperation> expectedGroupOps = Collections.singletonList( + GroupOperation.createModifyGroupOperation(createdGroup.id(), + Group.Type.SELECT, + updatedBuckets)); + internalProvider.validate(DID, expectedGroupOps); + Group existingGroup = groupService.getGroup(DID, removeKey); + List<Group> groupEntries = Collections.singletonList(existingGroup); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATED)); + } + + // Test group remove operations + private void testRemoveGroup() { + GroupKey currKey = new DefaultGroupKey("group1RemoveBuckets".getBytes()); + Group existingGroup = groupService.getGroup(DID, currKey); + groupService.removeGroup(DID, currKey, appId); + List<GroupOperation> expectedGroupOps = Collections.singletonList( + GroupOperation.createDeleteGroupOperation(existingGroup.id(), + Group.Type.SELECT)); + internalProvider.validate(DID, expectedGroupOps); + List<Group> groupEntries = Collections.emptyList(); + providerService.pushGroupMetrics(DID, groupEntries); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_REMOVED)); + } + + /** + * Test GroupOperationFailure function in Group Manager. + * a)GroupAddFailure + * b)GroupUpdateFailure + * c)GroupRemoteFailure + */ + @Test + public void testGroupOperationFailure() { + PortNumber[] ports1 = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + PortNumber[] ports2 = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + // Test Group creation before AUDIT process + GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes()); + List<GroupBucket> buckets = new ArrayList<>(); + List<PortNumber> outPorts = new ArrayList<>(); + outPorts.addAll(Arrays.asList(ports1)); + outPorts.addAll(Arrays.asList(ports2)); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + GroupDescription newGroupDesc = new DefaultGroupDescription(DID, + Group.Type.SELECT, + groupBuckets, + key, + null, + appId); + groupService.addGroup(newGroupDesc); + + // Test initial group audit process + GroupId gId1 = new DefaultGroupId(1); + Group group1 = createSouthboundGroupEntry(gId1, + Arrays.asList(ports1), + 0); + GroupId gId2 = new DefaultGroupId(2); + // Non zero reference count will make the group manager to queue + // the extraneous groups until reference count is zero. + Group group2 = createSouthboundGroupEntry(gId2, + Arrays.asList(ports2), + 2); + List<Group> groupEntries = Arrays.asList(group1, group2); + providerService.pushGroupMetrics(DID, groupEntries); + Group createdGroup = groupService.getGroup(DID, key); + + // Group Add failure test + GroupOperation groupAddOp = GroupOperation. + createAddGroupOperation(createdGroup.id(), + createdGroup.type(), + createdGroup.buckets()); + providerService.groupOperationFailed(DID, groupAddOp); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_ADD_FAILED)); + + // Group Mod failure test + groupService.addGroup(newGroupDesc); + createdGroup = groupService.getGroup(DID, key); + assertNotNull(createdGroup); + + GroupOperation groupModOp = GroupOperation. + createModifyGroupOperation(createdGroup.id(), + createdGroup.type(), + createdGroup.buckets()); + providerService.groupOperationFailed(DID, groupModOp); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATE_FAILED)); + + // Group Delete failure test + groupService.addGroup(newGroupDesc); + createdGroup = groupService.getGroup(DID, key); + assertNotNull(createdGroup); + + GroupOperation groupDelOp = GroupOperation. + createDeleteGroupOperation(createdGroup.id(), + createdGroup.type()); + providerService.groupOperationFailed(DID, groupDelOp); + internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_REMOVE_FAILED)); + + } + + private Group createSouthboundGroupEntry(GroupId gId, + List<PortNumber> ports, + long referenceCount) { + List<PortNumber> outPorts = new ArrayList<>(); + outPorts.addAll(ports); + + List<GroupBucket> buckets = new ArrayList<>(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + StoredGroupEntry group = new DefaultGroup( + gId, DID, Group.Type.SELECT, groupBuckets); + group.setReferenceCount(referenceCount); + return group; + } + + private static class TestGroupListener implements GroupListener { + final List<GroupEvent> events = new ArrayList<>(); + + @Override + public void event(GroupEvent event) { + events.add(event); + } + + public void validateEvent(List<GroupEvent.Type> expectedEvents) { + int i = 0; + System.err.println("events :" + events); + for (GroupEvent e : events) { + assertEquals("unexpected event", expectedEvents.get(i), e.type()); + i++; + } + assertEquals("mispredicted number of events", + expectedEvents.size(), events.size()); + events.clear(); + } + } + + private class TestGroupProvider + extends AbstractProvider implements GroupProvider { + DeviceId lastDeviceId; + List<GroupOperation> groupOperations = new ArrayList<>(); + + protected TestGroupProvider(ProviderId id) { + super(id); + } + + @Override + public void performGroupOperation(DeviceId deviceId, + GroupOperations groupOps) { + lastDeviceId = deviceId; + groupOperations.addAll(groupOps.operations()); + } + + public void validate(DeviceId expectedDeviceId, + List<GroupOperation> expectedGroupOps) { + if (expectedGroupOps == null) { + assertTrue("events generated", groupOperations.isEmpty()); + return; + } + + assertEquals(lastDeviceId, expectedDeviceId); + assertTrue((this.groupOperations.containsAll(expectedGroupOps) && + expectedGroupOps.containsAll(groupOperations))); + + groupOperations.clear(); + lastDeviceId = null; + } + + } + +} + + diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java new file mode 100644 index 00000000..e7f14b5d --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014-2015 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.net.host.impl; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigApplyDelegate; +import org.onosproject.net.config.basics.BasicHostConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.DeviceId; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.PortNumber; +import org.onosproject.net.host.DefaultHostDescription; +import org.onosproject.net.host.HostDescription; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +public class BasicHostOperatorTest { + private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01"); + private static final VlanId VLAN = VlanId.vlanId((short) 10); + private static final IpAddress IP = IpAddress.valueOf("10.0.0.1"); + + private static final HostId ID = HostId.hostId(MAC); + private static final HostLocation LOC = new HostLocation( + DeviceId.deviceId("of:foo"), + PortNumber.portNumber(100), + 123L + ); + private static final HostDescription HOST = new DefaultHostDescription(MAC, VLAN, LOC, IP); + + private final ConfigApplyDelegate delegate = new ConfigApplyDelegate() { + @Override + public void onApply(Config config) { + } + }; + private final ObjectMapper mapper = new ObjectMapper(); + + private static final BasicHostConfig BHC = new BasicHostConfig(); + private static final String NAME = "testhost"; + private static final double LAT = 40.96; + + @Before + public void setUp() { + BHC.init(ID, "test", JsonNodeFactory.instance.objectNode(), mapper, delegate); + BHC.name(NAME).latitude(40.96); + } + + @Test + public void testDescOps() { + HostDescription desc = BasicHostOperator.combine(BHC, HOST); + assertEquals(NAME, desc.annotations().value(AnnotationKeys.NAME)); + assertEquals(null, desc.annotations().value(AnnotationKeys.LONGITUDE)); + assertEquals(String.valueOf(LAT), desc.annotations().value(AnnotationKeys.LATITUDE)); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java new file mode 100644 index 00000000..dbb807f8 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java @@ -0,0 +1,529 @@ +/* + * Copyright 2014-2015 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.net.host.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; +import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED; +import static org.onosproject.net.host.HostEvent.Type.HOST_MOVED; +import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED; +import static org.onosproject.net.host.HostEvent.Type.HOST_UPDATED; + +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.event.Event; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.net.config.NetworkConfigServiceAdapter; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.PortNumber; +import org.onosproject.net.host.DefaultHostDescription; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostEvent; +import org.onosproject.net.host.HostListener; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.HostProviderRegistry; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.host.InterfaceIpAddress; +import org.onosproject.net.host.PortAddresses; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.trivial.SimpleHostStore; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * Test codifying the host service & host provider service contracts. + */ +public class HostManagerTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + + private static final VlanId VLAN1 = VlanId.vlanId((short) 1); + private static final VlanId VLAN2 = VlanId.vlanId((short) 2); + private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01"); + private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02"); + private static final MacAddress MAC3 = MacAddress.valueOf("00:00:33:00:00:03"); + private static final MacAddress MAC4 = MacAddress.valueOf("00:00:44:00:00:04"); + 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, VLAN1); + + private static final IpAddress IP1 = IpAddress.valueOf("10.0.0.1"); + private static final IpAddress IP2 = IpAddress.valueOf("10.0.0.2"); + private static final IpAddress IP3 = IpAddress.valueOf("2001::1"); + private static final IpAddress IP4 = IpAddress.valueOf("2001::2"); + + private static final DeviceId DID1 = DeviceId.deviceId("of:001"); + private static final DeviceId DID2 = DeviceId.deviceId("of:002"); + private static final PortNumber P1 = PortNumber.portNumber(100); + private static final PortNumber P2 = PortNumber.portNumber(200); + private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L); + private static final HostLocation LOC2 = new HostLocation(DID1, P2, 123L); + private static final ConnectPoint CP1 = new ConnectPoint(DID1, P1); + private static final ConnectPoint CP2 = new ConnectPoint(DID2, P2); + + private static final InterfaceIpAddress IA1 = + new InterfaceIpAddress(IpAddress.valueOf("10.1.1.1"), + IpPrefix.valueOf("10.1.1.0/24")); + private static final InterfaceIpAddress IA2 = + new InterfaceIpAddress(IpAddress.valueOf("10.2.2.2"), + IpPrefix.valueOf("10.2.0.0/16")); + private static final InterfaceIpAddress IA3 = + new InterfaceIpAddress(IpAddress.valueOf("10.3.3.3"), + IpPrefix.valueOf("10.3.3.0/24")); + private static final InterfaceIpAddress IA4 = + new InterfaceIpAddress(IpAddress.valueOf("2001:100::1"), + IpPrefix.valueOf("2001:100::/56")); + private static final InterfaceIpAddress IA5 = + new InterfaceIpAddress(IpAddress.valueOf("2001:200::1"), + IpPrefix.valueOf("2001:200::/48")); + private static final InterfaceIpAddress IA6 = + new InterfaceIpAddress(IpAddress.valueOf("2001:300::1"), + IpPrefix.valueOf("2001:300::/56")); + + private HostManager mgr; + + protected TestListener listener = new TestListener(); + protected HostProviderRegistry registry; + protected TestHostProvider provider; + protected HostProviderService providerService; + + @Before + public void setUp() { + mgr = new HostManager(); + mgr.store = new SimpleHostStore(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + registry = mgr; + mgr.networkConfigService = new TestNetworkConfigService(); + mgr.activate(); + + mgr.addListener(listener); + + provider = new TestHostProvider(); + providerService = registry.register(provider); + assertTrue("provider should be registered", + registry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + registry.unregister(provider); + assertFalse("provider should not be registered", + registry.getProviders().contains(provider.id())); + + mgr.removeListener(listener); + mgr.deactivate(); + injectEventDispatcher(mgr, null); + } + + private void detect(HostId hid, MacAddress mac, VlanId vlan, + HostLocation loc, IpAddress ip) { + HostDescription descr = new DefaultHostDescription(mac, vlan, loc, ip); + providerService.hostDetected(hid, descr); + assertNotNull("host should be found", mgr.getHost(hid)); + } + + private void validateEvents(Enum... types) { + int i = 0; + assertEquals("wrong events received", types.length, listener.events.size()); + for (Event event : listener.events) { + assertEquals("incorrect event type", types[i], event.type()); + i++; + } + listener.events.clear(); + } + + @Test + public void hostDetected() { + assertNull("host shouldn't be found", mgr.getHost(HID1)); + + // host addition + detect(HID1, MAC1, VLAN1, LOC1, IP1); + assertEquals("exactly one should be found", 1, mgr.getHostCount()); + detect(HID2, MAC2, VLAN2, LOC2, IP1); + assertEquals("two hosts should be found", 2, mgr.getHostCount()); + validateEvents(HOST_ADDED, HOST_ADDED); + + // host motion + detect(HID1, MAC1, VLAN1, LOC2, IP1); + validateEvents(HOST_MOVED); + assertEquals("only two hosts should be found", 2, mgr.getHostCount()); + + // host update + detect(HID1, MAC1, VLAN1, LOC2, IP2); + validateEvents(HOST_UPDATED); + assertEquals("only two hosts should be found", 2, mgr.getHostCount()); + } + + @Test + public void hostDetectedIPv6() { + assertNull("host shouldn't be found", mgr.getHost(HID3)); + + // host addition + detect(HID3, MAC3, VLAN1, LOC1, IP3); + assertEquals("exactly one should be found", 1, mgr.getHostCount()); + detect(HID4, MAC4, VLAN2, LOC2, IP3); + assertEquals("two hosts should be found", 2, mgr.getHostCount()); + validateEvents(HOST_ADDED, HOST_ADDED); + + // host motion + detect(HID3, MAC3, VLAN1, LOC2, IP3); + validateEvents(HOST_MOVED); + assertEquals("only two hosts should be found", 2, mgr.getHostCount()); + + // host update + detect(HID3, MAC3, VLAN1, LOC2, IP4); + validateEvents(HOST_UPDATED); + assertEquals("only two hosts should be found", 2, mgr.getHostCount()); + } + + @Test + public void hostVanished() { + detect(HID1, MAC1, VLAN1, LOC1, IP1); + providerService.hostVanished(HID1); + validateEvents(HOST_ADDED, HOST_REMOVED); + + assertNull("host should have been removed", mgr.getHost(HID1)); + } + + @Test + public void hostVanishedIPv6() { + detect(HID3, MAC3, VLAN1, LOC1, IP3); + providerService.hostVanished(HID3); + validateEvents(HOST_ADDED, HOST_REMOVED); + + assertNull("host should have been removed", mgr.getHost(HID3)); + } + + private void validateHosts( + String msg, Iterable<Host> hosts, HostId... ids) { + Set<HostId> hids = Sets.newHashSet(ids); + for (Host h : hosts) { + assertTrue(msg, hids.remove(h.id())); + } + assertTrue("expected hosts not fetched from store", hids.isEmpty()); + } + + @Test + public void getHosts() { + detect(HID1, MAC1, VLAN1, LOC1, IP1); + detect(HID2, MAC2, VLAN1, LOC2, IP2); + + validateHosts("host not properly stored", mgr.getHosts(), HID1, HID2); + validateHosts("can't get hosts by VLAN", mgr.getHostsByVlan(VLAN1), HID1, HID2); + validateHosts("can't get hosts by MAC", mgr.getHostsByMac(MAC1), HID1); + validateHosts("can't get hosts by IP", mgr.getHostsByIp(IP1), HID1); + validateHosts("can't get hosts by location", mgr.getConnectedHosts(LOC1), HID1); + assertTrue("incorrect host location", mgr.getConnectedHosts(DID2).isEmpty()); + } + + @Test + public void getHostsIPv6() { + detect(HID3, MAC3, VLAN1, LOC1, IP3); + detect(HID4, MAC4, VLAN1, LOC2, IP4); + + validateHosts("host not properly stored", mgr.getHosts(), HID3, HID4); + validateHosts("can't get hosts by VLAN", mgr.getHostsByVlan(VLAN1), HID3, HID4); + validateHosts("can't get hosts by MAC", mgr.getHostsByMac(MAC3), HID3); + validateHosts("can't get hosts by IP", mgr.getHostsByIp(IP3), HID3); + validateHosts("can't get hosts by location", mgr.getConnectedHosts(LOC1), HID3); + assertTrue("incorrect host location", mgr.getConnectedHosts(DID2).isEmpty()); + } + + private static class TestHostProvider extends AbstractProvider + implements HostProvider { + + protected TestHostProvider() { + super(PID); + } + + @Override + public ProviderId id() { + return PID; + } + + @Override + public void triggerProbe(Host host) { + } + + } + + private static class TestListener implements HostListener { + + protected List<HostEvent> events = Lists.newArrayList(); + + @Override + public void event(HostEvent event) { + events.add(event); + } + + } + + @Test + public void bindAddressesToPort() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + // Add some more addresses and check that they're added correctly + PortAddresses add2 = + new PortAddresses(CP1, Sets.newHashSet(IA3), null, + VlanId.vlanId((short) 2)); + + mgr.bindAddressesToPort(add2); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(2, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + assertTrue(storedAddresses.contains(add2)); + + PortAddresses add3 = new PortAddresses(CP1, null, MAC2, VlanId.NONE); + + mgr.bindAddressesToPort(add3); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(3, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + assertTrue(storedAddresses.contains(add2)); + assertTrue(storedAddresses.contains(add3)); + } + + @Test + public void bindAddressesToPortIPv6() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + // Add some more addresses and check that they're added correctly + PortAddresses add2 = + new PortAddresses(CP1, Sets.newHashSet(IA6), null, + VlanId.vlanId((short) 2)); + + mgr.bindAddressesToPort(add2); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(2, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + assertTrue(storedAddresses.contains(add2)); + + PortAddresses add3 = new PortAddresses(CP1, null, MAC4, VlanId.NONE); + + mgr.bindAddressesToPort(add3); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(3, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + assertTrue(storedAddresses.contains(add2)); + assertTrue(storedAddresses.contains(add3)); + } + + @Test + public void unbindAddressesFromPort() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + PortAddresses rem1 = + new PortAddresses(CP1, Sets.newHashSet(IA1), null, VlanId.NONE); + + mgr.unbindAddressesFromPort(rem1); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + // It shouldn't have been removed because it didn't match the originally + // submitted address object + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + mgr.unbindAddressesFromPort(add1); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertTrue(storedAddresses.isEmpty()); + } + + @Test + public void unbindAddressesFromPortIPv6() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + PortAddresses rem1 = + new PortAddresses(CP1, Sets.newHashSet(IA4), null, VlanId.NONE); + + mgr.unbindAddressesFromPort(rem1); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + // It shouldn't have been removed because it didn't match the originally + // submitted address object + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + mgr.unbindAddressesFromPort(add1); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertTrue(storedAddresses.isEmpty()); + } + + @Test + public void clearAddresses() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + mgr.clearAddresses(CP1); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertTrue(storedAddresses.isEmpty()); + } + + @Test + public void clearAddressesIPv6() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + + mgr.clearAddresses(CP1); + storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertTrue(storedAddresses.isEmpty()); + } + + @Test + public void getAddressBindingsForPort() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + } + + @Test + public void getAddressBindingsForPortIPv6() { + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1); + + assertEquals(1, storedAddresses.size()); + assertTrue(storedAddresses.contains(add1)); + } + + @Test + public void getAddressBindings() { + Set<PortAddresses> storedAddresses = mgr.getAddressBindings(); + + assertTrue(storedAddresses.isEmpty()); + + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + + storedAddresses = mgr.getAddressBindings(); + + assertTrue(storedAddresses.size() == 1); + + PortAddresses add2 = + new PortAddresses(CP2, Sets.newHashSet(IA3), MAC2, VlanId.NONE); + + mgr.bindAddressesToPort(add2); + + storedAddresses = mgr.getAddressBindings(); + + assertTrue(storedAddresses.size() == 2); + assertTrue(storedAddresses.equals(Sets.newHashSet(add1, add2))); + } + + @Test + public void getAddressBindingsIPv6() { + Set<PortAddresses> storedAddresses = mgr.getAddressBindings(); + + assertTrue(storedAddresses.isEmpty()); + + PortAddresses add1 = + new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE); + + mgr.bindAddressesToPort(add1); + + storedAddresses = mgr.getAddressBindings(); + + assertTrue(storedAddresses.size() == 1); + + PortAddresses add2 = + new PortAddresses(CP2, Sets.newHashSet(IA5), MAC4, VlanId.NONE); + + mgr.bindAddressesToPort(add2); + + storedAddresses = mgr.getAddressBindings(); + + assertTrue(storedAddresses.size() == 2); + assertTrue(storedAddresses.equals(Sets.newHashSet(add1, add2))); + } + + private class TestNetworkConfigService extends NetworkConfigServiceAdapter { + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java new file mode 100644 index 00000000..d6ff473a --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java @@ -0,0 +1,320 @@ +/* + * Copyright 2014-2015 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.net.host.impl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import org.junit.After; +import org.junit.Test; +import org.onlab.packet.ARP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.InterfaceIpAddress; +import org.onosproject.net.host.PortAddresses; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketServiceAdapter; +import org.onosproject.net.provider.ProviderId; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class HostMonitorTest { + + private static final IpAddress TARGET_IP_ADDR = + IpAddress.valueOf("10.0.0.1"); + private static final IpAddress SOURCE_ADDR = + IpAddress.valueOf("10.0.0.99"); + private static final InterfaceIpAddress IA1 = + new InterfaceIpAddress(SOURCE_ADDR, IpPrefix.valueOf("10.0.0.0/24")); + private MacAddress sourceMac = MacAddress.valueOf(1L); + + private HostMonitor hostMonitor; + + @After + public void shutdown() { + hostMonitor.shutdown(); + } + + @Test + public void testMonitorHostExists() throws Exception { + ProviderId id = new ProviderId("fake://", "id"); + + Host host = createMock(Host.class); + expect(host.providerId()).andReturn(id); + replay(host); + + HostManager hostManager = createMock(HostManager.class); + expect(hostManager.getHostsByIp(TARGET_IP_ADDR)) + .andReturn(Collections.singleton(host)); + replay(hostManager); + + HostProvider hostProvider = createMock(HostProvider.class); + expect(hostProvider.id()).andReturn(id).anyTimes(); + hostProvider.triggerProbe(host); + expectLastCall().once(); + replay(hostProvider); + + hostMonitor = new HostMonitor(null, hostManager, null); + + hostMonitor.registerHostProvider(hostProvider); + hostMonitor.addMonitoringFor(TARGET_IP_ADDR); + + hostMonitor.run(null); + + verify(hostProvider); + } + + @Test + public void testMonitorHostDoesNotExist() throws Exception { + + HostManager hostManager = createMock(HostManager.class); + + DeviceId devId = DeviceId.deviceId("fake"); + + Device device = createMock(Device.class); + expect(device.id()).andReturn(devId).anyTimes(); + replay(device); + + PortNumber portNum = PortNumber.portNumber(1L); + + Port port = createMock(Port.class); + expect(port.number()).andReturn(portNum).anyTimes(); + replay(port); + + TestDeviceService deviceService = new TestDeviceService(); + deviceService.addDevice(device, Collections.singleton(port)); + + ConnectPoint cp = new ConnectPoint(devId, portNum); + PortAddresses pa = + new PortAddresses(cp, Collections.singleton(IA1), sourceMac, VlanId.NONE); + + expect(hostManager.getHostsByIp(TARGET_IP_ADDR)) + .andReturn(Collections.emptySet()).anyTimes(); + replay(hostManager); + + InterfaceService interfaceService = createMock(InterfaceService.class); + expect(interfaceService.getMatchingInterface(TARGET_IP_ADDR)) + .andReturn(new Interface(cp, Collections.singleton(IA1), sourceMac, VlanId.NONE)) + .anyTimes(); + replay(interfaceService); + + TestPacketService packetService = new TestPacketService(); + + + // Run the test + hostMonitor = new HostMonitor(packetService, hostManager, interfaceService); + + hostMonitor.addMonitoringFor(TARGET_IP_ADDR); + hostMonitor.run(null); + + + // Check that a packet was sent to our PacketService and that it has + // the properties we expect + assertEquals(1, packetService.packets.size()); + OutboundPacket packet = packetService.packets.get(0); + + // Check the output port is correct + assertEquals(1, packet.treatment().immediate().size()); + Instruction instruction = packet.treatment().immediate().get(0); + assertTrue(instruction instanceof OutputInstruction); + OutputInstruction oi = (OutputInstruction) instruction; + assertEquals(portNum, oi.port()); + + // Check the output packet is correct (well the important bits anyway) + final byte[] pktData = new byte[packet.data().remaining()]; + packet.data().get(pktData); + Ethernet eth = Ethernet.deserializer().deserialize(pktData, 0, pktData.length); + assertEquals(Ethernet.VLAN_UNTAGGED, eth.getVlanID()); + ARP arp = (ARP) eth.getPayload(); + assertArrayEquals(SOURCE_ADDR.toOctets(), + arp.getSenderProtocolAddress()); + assertArrayEquals(sourceMac.toBytes(), + arp.getSenderHardwareAddress()); + assertArrayEquals(TARGET_IP_ADDR.toOctets(), + arp.getTargetProtocolAddress()); + } + + @Test + public void testMonitorHostDoesNotExistWithVlan() throws Exception { + + HostManager hostManager = createMock(HostManager.class); + + DeviceId devId = DeviceId.deviceId("fake"); + short vlan = 5; + + Device device = createMock(Device.class); + expect(device.id()).andReturn(devId).anyTimes(); + replay(device); + + PortNumber portNum = PortNumber.portNumber(1L); + + Port port = createMock(Port.class); + expect(port.number()).andReturn(portNum).anyTimes(); + replay(port); + + TestDeviceService deviceService = new TestDeviceService(); + deviceService.addDevice(device, Collections.singleton(port)); + + ConnectPoint cp = new ConnectPoint(devId, portNum); + PortAddresses pa = + new PortAddresses(cp, Collections.singleton(IA1), sourceMac, + VlanId.vlanId(vlan)); + + expect(hostManager.getHostsByIp(TARGET_IP_ADDR)) + .andReturn(Collections.emptySet()).anyTimes(); + replay(hostManager); + + InterfaceService interfaceService = createMock(InterfaceService.class); + expect(interfaceService.getMatchingInterface(TARGET_IP_ADDR)) + .andReturn(new Interface(cp, Collections.singleton(IA1), sourceMac, VlanId.vlanId(vlan))) + .anyTimes(); + replay(interfaceService); + + TestPacketService packetService = new TestPacketService(); + + + // Run the test + hostMonitor = new HostMonitor(packetService, hostManager, interfaceService); + + hostMonitor.addMonitoringFor(TARGET_IP_ADDR); + hostMonitor.run(null); + + + // Check that a packet was sent to our PacketService and that it has + // the properties we expect + assertEquals(1, packetService.packets.size()); + OutboundPacket packet = packetService.packets.get(0); + + // Check the output port is correct + assertEquals(1, packet.treatment().immediate().size()); + Instruction instruction = packet.treatment().immediate().get(0); + assertTrue(instruction instanceof OutputInstruction); + OutputInstruction oi = (OutputInstruction) instruction; + assertEquals(portNum, oi.port()); + + // Check the output packet is correct (well the important bits anyway) + final byte[] pktData = new byte[packet.data().remaining()]; + packet.data().get(pktData); + Ethernet eth = Ethernet.deserializer().deserialize(pktData, 0, pktData.length); + assertEquals(vlan, eth.getVlanID()); + ARP arp = (ARP) eth.getPayload(); + assertArrayEquals(SOURCE_ADDR.toOctets(), + arp.getSenderProtocolAddress()); + assertArrayEquals(sourceMac.toBytes(), + arp.getSenderHardwareAddress()); + assertArrayEquals(TARGET_IP_ADDR.toOctets(), + arp.getTargetProtocolAddress()); + } + + class TestPacketService extends PacketServiceAdapter { + + List<OutboundPacket> packets = new ArrayList<>(); + + @Override + public void emit(OutboundPacket packet) { + packets.add(packet); + } + } + + class TestDeviceService extends DeviceServiceAdapter { + + List<Device> devices = Lists.newArrayList(); + Multimap<DeviceId, Port> devicePorts = HashMultimap.create(); + + void addDevice(Device device, Set<Port> ports) { + devices.add(device); + for (Port p : ports) { + devicePorts.put(device.id(), p); + } + } + + @Override + public int getDeviceCount() { + return 0; + } + + @Override + public Iterable<Device> getDevices() { + return devices; + } + + @Override + public Device getDevice(DeviceId deviceId) { + return null; + } + + @Override + public MastershipRole getRole(DeviceId deviceId) { + return null; + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + List<Port> ports = Lists.newArrayList(); + for (Port p : devicePorts.get(deviceId)) { + ports.add(p); + } + return ports; + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + return null; + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + return false; + } + + @Override + public void addListener(DeviceListener listener) { + } + + @Override + public void removeListener(DeviceListener listener) { + } + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java new file mode 100644 index 00000000..d34143c9 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014 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.net.intent; + +import java.util.Collection; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.onosproject.net.EdgeLink; +import org.onosproject.net.Link; + +/** + * Matcher to determine if a Collection of Links contains a path between a source + * and a destination. + */ +public class LinksHaveEntryWithSourceDestinationPairMatcher extends + TypeSafeMatcher<Collection<Link>> { + private final String source; + private final String destination; + + /** + * Creates a matcher for a given path represented by a source and + * a destination. + * + * @param source string identifier for the source of the path + * @param destination string identifier for the destination of the path + */ + LinksHaveEntryWithSourceDestinationPairMatcher(String source, + String destination) { + this.source = source; + this.destination = destination; + } + + @Override + public boolean matchesSafely(Collection<Link> links) { + for (Link link : links) { + if (source.equals(destination) && link instanceof EdgeLink) { + EdgeLink edgeLink = (EdgeLink) link; + if (edgeLink.hostLocation().elementId() + .toString().endsWith(source)) { + return true; + } + } + + if (link.src().elementId().toString().endsWith(source) && + link.dst().elementId().toString().endsWith(destination)) { + return true; + } + } + + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("link lookup for source \""); + description.appendText(source); + description.appendText(" and destination "); + description.appendText(destination); + description.appendText("\""); + } + + @Override + public void describeMismatchSafely(Collection<Link> links, + Description mismatchDescription) { + mismatchDescription.appendText("was "). + appendText(links.toString()); + } + + /** + * Creates a link has path matcher. + * + * @param source string identifier for the source of the path + * @param destination string identifier for the destination of the path + * @return matcher to match the path + */ + public static LinksHaveEntryWithSourceDestinationPairMatcher linksHasPath( + String source, + String destination) { + return new LinksHaveEntryWithSourceDestinationPairMatcher(source, + destination); + } +} + diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java new file mode 100644 index 00000000..219d2fd5 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015 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.net.intent.impl; + +import java.util.Collection; +import java.util.List; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentBatchDelegate; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentState; +import org.onosproject.net.intent.IntentTestsMocks.MockIntent; +import org.onosproject.net.intent.IntentTestsMocks.MockTimestamp; +import org.onosproject.net.intent.MockIdGenerator; + +import com.google.common.collect.ImmutableList; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +/** + * Unit tests for the intent accumulator. + */ +public class IntentAccumulatorTest { + + Intent intent1; + Intent intent2; + Intent intent3; + IdGenerator mockGenerator; + + private static IntentDataMatcher containsIntent(Intent intent) { + return new IntentDataMatcher(intent); + } + + /** + * Creates mock intents used by the test. + */ + @Before + public void localSetup() { + mockGenerator = new MockIdGenerator(); + Intent.bindIdGenerator(mockGenerator); + + intent1 = new MockIntent(1L); + intent2 = new MockIntent(2L); + intent3 = new MockIntent(3L); + } + + /** + * Removes id generator from the Intent class. + */ + @After + public void localTearDown() { + Intent.unbindIdGenerator(mockGenerator); + } + + /** + * Hamcrest matcher to check that a collection of intent data objects + * contains an entry for a given intent. + */ + private static final class IntentDataMatcher + extends TypeSafeDiagnosingMatcher<Collection<IntentData>> { + + final Intent intent; + + public IntentDataMatcher(Intent intent) { + this.intent = intent; + } + + /** + * Check that the given collection of intent data contains a specific + * intent. + * + * @param operations collection of intent data + * @param description description + * @return true if the collection contains the intent, false otherwise. + */ + public boolean matchesSafely(Collection<IntentData> operations, + Description description) { + for (IntentData operation : operations) { + if (operation.key().equals(intent.key())) { + if (operation.state() != IntentState.INSTALLED) { + description.appendText("state was " + operation.state()); + return false; + } + return true; + } + } + description.appendText("key was not found " + intent.key()); + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("INSTALLED state intent with key " + intent.key()); + } + } + + /** + * Mock batch delegate class. Gets calls from the accumulator and checks + * that the operations have been properly compressed. + */ + private class MockIntentBatchDelegate + implements IntentBatchDelegate { + public void execute(Collection<IntentData> operations) { + assertThat(operations, hasSize(3)); + assertThat(operations, containsIntent(intent1)); + assertThat(operations, containsIntent(intent2)); + assertThat(operations, containsIntent(intent3)); + } + } + + /** + * Tests that the accumulator properly compresses operations on the same + * intents. + */ + @Test + public void checkAccumulator() { + + MockIntentBatchDelegate delegate = new MockIntentBatchDelegate(); + IntentAccumulator accumulator = new IntentAccumulator(delegate); + + List<IntentData> intentDataItems = ImmutableList.of( + new IntentData(intent1, IntentState.INSTALLING, + new MockTimestamp(1)), + new IntentData(intent2, IntentState.INSTALLING, + new MockTimestamp(1)), + new IntentData(intent3, IntentState.INSTALLED, + new MockTimestamp(1)), + new IntentData(intent2, IntentState.INSTALLED, + new MockTimestamp(1)), + new IntentData(intent2, IntentState.INSTALLED, + new MockTimestamp(1)), + new IntentData(intent1, IntentState.INSTALLED, + new MockTimestamp(1))); + + + accumulator.processItems(intentDataItems); + } + + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java new file mode 100644 index 00000000..0f6ce67c --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java @@ -0,0 +1,261 @@ +/* + * Copyright 2015 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.net.intent.impl; + +import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentServiceAdapter; +import org.onosproject.net.intent.IntentStore; +import org.onosproject.net.intent.IntentStoreDelegate; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.store.Timestamp; +import org.onosproject.store.trivial.SimpleIntentStore; +import org.onosproject.store.trivial.SystemClockTimestamp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.onosproject.net.intent.IntentState.*; +import static org.onosproject.net.intent.IntentTestsMocks.MockIntent; + +/** + * Test intent cleanup. + */ +public class IntentCleanupTest { + + private IntentCleanup cleanup; + private MockIntentService service; + private IntentStore store; + protected IdGenerator idGenerator; // global or one per test? per test for now. + + private static class MockIntentService extends IntentServiceAdapter { + + private int submitCounter = 0; + + @Override + public void submit(Intent intent) { + submitCounter++; + } + + public int submitCounter() { + return submitCounter; + } + } + + @Before + public void setUp() { + service = new MockIntentService(); + store = new SimpleIntentStore(); + cleanup = new IntentCleanup(); + idGenerator = new MockIdGenerator(); + + cleanup.cfgService = new ComponentConfigAdapter(); + cleanup.service = service; + cleanup.store = store; + cleanup.period = 10; + cleanup.retryThreshold = 3; + cleanup.activate(); + + assertTrue("store should be empty", + Sets.newHashSet(cleanup.store.getIntents()).isEmpty()); + + Intent.bindIdGenerator(idGenerator); + } + + @After + public void tearDown() { + cleanup.deactivate(); + + Intent.unbindIdGenerator(idGenerator); + } + + /** + * Trigger resubmit of intent in CORRUPT during periodic poll. + */ + @Test + public void corruptPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) {} + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + Timestamp version = new SystemClockTimestamp(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, version); + store.addPending(data); + + cleanup.run(); //FIXME broken? + assertEquals("Expect number of submits incorrect", + 1, service.submitCounter()); + } + + /** + * Trigger resubmit of intent in INSTALL_REQ for too long. + */ + @Test + public void pendingPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) {} + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + Timestamp version = new SystemClockTimestamp(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, version); + store.addPending(data); + + cleanup.run(); + assertEquals("Expect number of submits incorrect", + 1, service.submitCounter()); + + } + + /** + * Trigger resubmit of intent in INSTALLING for too long. + */ + @Test + public void installingPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(INSTALLING); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + Timestamp version = new SystemClockTimestamp(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, version); + store.addPending(data); + + cleanup.run(); + assertEquals("Expect number of submits incorrect", + 1, service.submitCounter()); + + } + + /** + * Only submit one of two intents because one is too new. + */ + @Test + public void skipPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) {} + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, null); + store.addPending(data); + + Intent intent2 = new MockIntent(2L); + Timestamp version = new SystemClockTimestamp(1L); + data = new IntentData(intent2, INSTALL_REQ, version); + store.addPending(data); + + cleanup.run(); + assertEquals("Expect number of submits incorrect", + 1, service.submitCounter()); + } + + /** + * Verify resubmit in response to CORRUPT event. + */ + @Test + public void corruptEvent() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + + Intent intent = new MockIntent(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, null); + + store.addPending(data); + assertEquals("Expect number of submits incorrect", + 1, service.submitCounter()); + } + + /** + * Intent should not be retried because threshold is reached. + */ + @Test + public void corruptEventThreshold() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + intentData.setErrorCount(cleanup.retryThreshold); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, null); + + store.addPending(data); + assertEquals("Expect number of submits incorrect", + 0, service.submitCounter()); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java new file mode 100644 index 00000000..15ee24e6 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java @@ -0,0 +1,285 @@ +/* + * Copyright 2015 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.net.intent.impl; + +import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentStore; +import org.onosproject.net.intent.IntentStoreDelegate; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.store.Timestamp; +import org.onosproject.store.trivial.SimpleIntentStore; +import org.onosproject.store.trivial.SystemClockTimestamp; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.assertTrue; +import static org.onosproject.net.intent.IntentState.*; +import static org.onosproject.net.intent.IntentTestsMocks.MockIntent; + +/** + * Test intent cleanup using Mocks. + * FIXME remove this or IntentCleanupTest + */ +public class IntentCleanupTestMock { + + private IntentCleanup cleanup; + private IntentService service; + private IntentStore store; + protected IdGenerator idGenerator; // global or one per test? per test for now. + + @Before + public void setUp() { + service = createMock(IntentService.class); + store = new SimpleIntentStore(); + cleanup = new IntentCleanup(); + idGenerator = new MockIdGenerator(); + + service.addListener(cleanup); + expectLastCall().once(); + replay(service); + + cleanup.cfgService = new ComponentConfigAdapter(); + cleanup.service = service; + cleanup.store = store; + cleanup.period = 1000; + cleanup.retryThreshold = 3; + cleanup.activate(); + + verify(service); + reset(service); + + assertTrue("store should be empty", + Sets.newHashSet(cleanup.store.getIntents()).isEmpty()); + + Intent.bindIdGenerator(idGenerator); + } + + @After + public void tearDown() { + service.removeListener(cleanup); + expectLastCall().once(); + replay(service); + + cleanup.deactivate(); + + verify(service); + reset(service); + + Intent.unbindIdGenerator(idGenerator); + } + + /** + * Trigger resubmit of intent in CORRUPT during periodic poll. + */ + @Test + public void corruptPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) {} + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + Timestamp version = new SystemClockTimestamp(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, version); + store.addPending(data); + + service.submit(intent); + expectLastCall().once(); + replay(service); + + cleanup.run(); //FIXME broken? + verify(service); + reset(service); + } + + /** + * Trigger resubmit of intent in INSTALL_REQ for too long. + */ + @Test + public void pendingPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) {} + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + Timestamp version = new SystemClockTimestamp(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, version); + store.addPending(data); + + service.submit(intent); + expectLastCall().once(); + replay(service); + + cleanup.run(); + verify(service); + reset(service); + } + + /** + * Trigger resubmit of intent in INSTALLING for too long. + */ + @Test + public void installingPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(INSTALLING); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + Timestamp version = new SystemClockTimestamp(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, version); + store.addPending(data); + + service.submit(intent); + expectLastCall().once(); + replay(service); + + cleanup.run(); + verify(service); + reset(service); + } + + /** + * Only submit one of two intents because one is too new. + */ + @Test + public void skipPoll() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) {} + }; + store.setDelegate(mockDelegate); + + Intent intent = new MockIntent(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, null); + store.addPending(data); + + Intent intent2 = new MockIntent(2L); + Timestamp version = new SystemClockTimestamp(1L); + data = new IntentData(intent2, INSTALL_REQ, version); + store.addPending(data); + + service.submit(intent2); + expectLastCall().once(); + replay(service); + + cleanup.run(); + verify(service); + reset(service); + } + + /** + * Verify resubmit in response to CORRUPT event. + */ + @Test + public void corruptEvent() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + + Intent intent = new MockIntent(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, null); + + service.submit(intent); + expectLastCall().once(); + replay(service); + + store.addPending(data); + + verify(service); + reset(service); + } + + /** + * Intent should not be retried because threshold is reached. + */ + @Test + public void corruptEventThreshold() { + IntentStoreDelegate mockDelegate = new IntentStoreDelegate() { + @Override + public void process(IntentData intentData) { + intentData.setState(CORRUPT); + intentData.setErrorCount(cleanup.retryThreshold); + store.write(intentData); + } + + @Override + public void notify(IntentEvent event) { + cleanup.event(event); + } + }; + store.setDelegate(mockDelegate); + + + Intent intent = new MockIntent(1L); + IntentData data = new IntentData(intent, INSTALL_REQ, null); + + replay(service); + + store.addPending(data); + + verify(service); + reset(service); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java new file mode 100644 index 00000000..4bf32f43 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java @@ -0,0 +1,672 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.impl.TestCoreManager; +import org.onosproject.net.NetworkResource; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentCompiler; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentEvent.Type; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.IntentId; +import org.onosproject.net.intent.IntentListener; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentState; +import org.onosproject.net.intent.Key; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.store.trivial.SimpleIntentStore; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; +import static org.onlab.junit.TestTools.assertAfter; +import static org.onlab.util.Tools.delay; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; +import static org.onosproject.net.intent.IntentState.*; +import static org.onosproject.net.intent.IntentTestsMocks.MockFlowRule; +import static org.onosproject.net.intent.IntentTestsMocks.MockIntent; + +/** + * Test intent manager and transitions. + * + * TODO implement the following tests: + * - {submit, withdraw, update, replace} intent + * - {submit, update, recompiling} intent with failed compilation + * - failed reservation + * - push timeout recovery + * - failed items recovery + * + * in general, verify intents store, flow store, and work queue + */ + +public class IntentManagerTest { + + private static final int SUBMIT_TIMEOUT_MS = 1000; + private static final ApplicationId APPID = new TestApplicationId("manager-test"); + + private IntentManager manager; + private MockFlowRuleService flowRuleService; + + protected IntentService service; + protected IntentExtensionService extensionService; + protected TestListener listener = new TestListener(); + protected TestIntentCompiler compiler = new TestIntentCompiler(); + + private static class TestListener implements IntentListener { + final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create(); + Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap(); + + @Override + public void event(IntentEvent event) { + events.put(event.type(), event); + if (latchMap.containsKey(event.type())) { + latchMap.get(event.type()).countDown(); + } + } + + public int getCounts(IntentEvent.Type type) { + return events.get(type).size(); + } + + public void setLatch(int count, IntentEvent.Type type) { + latchMap.put(type, new CountDownLatch(count)); + } + + public void await(IntentEvent.Type type) { + try { + assertTrue("Timed out waiting for: " + type, + latchMap.get(type).await(5, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private static class TestIntentTracker implements ObjectiveTrackerService { + private TopologyChangeDelegate delegate; + @Override + public void setDelegate(TopologyChangeDelegate delegate) { + this.delegate = delegate; + } + + @Override + public void unsetDelegate(TopologyChangeDelegate delegate) { + if (delegate.equals(this.delegate)) { + this.delegate = null; + } + } + + @Override + public void addTrackedResources(Key key, Collection<NetworkResource> resources) { + //TODO + } + + @Override + public void removeTrackedResources(Key key, Collection<NetworkResource> resources) { + //TODO + } + + @Override + public void trackIntent(IntentData intentData) { + //TODO + } + } + + private static class MockInstallableIntent extends FlowRuleIntent { + + public MockInstallableIntent() { + super(APPID, Collections.singletonList(new MockFlowRule(100))); + } + } + + private static class TestIntentCompiler implements IntentCompiler<MockIntent> { + @Override + public List<Intent> compile(MockIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + return Lists.newArrayList(new MockInstallableIntent()); + } + } + + private static class TestIntentCompilerMultipleFlows implements IntentCompiler<MockIntent> { + @Override + public List<Intent> compile(MockIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + + return IntStream.rangeClosed(1, 5) + .mapToObj(mock -> (new MockInstallableIntent())) + .collect(Collectors.toList()); + } + } + + + private static class TestIntentCompilerError implements IntentCompiler<MockIntent> { + @Override + public List<Intent> compile(MockIntent intent, List<Intent> installable, + Set<LinkResourceAllocations> resources) { + throw new IntentCompilationException("Compilation always fails"); + } + } + + /** + * Hamcrest matcher to check that a collection of Intents contains an + * Intent with the specified Intent Id. + */ + public static class EntryForIntentMatcher extends TypeSafeMatcher<Collection<Intent>> { + private final IntentId id; + + public EntryForIntentMatcher(IntentId idValue) { + id = idValue; + } + + @Override + public boolean matchesSafely(Collection<Intent> intents) { + for (Intent intent : intents) { + if (intent.id().equals(id)) { + return true; + } + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("an intent with id \" "). + appendText(id.toString()). + appendText("\""); + } + } + + private static EntryForIntentMatcher hasIntentWithId(IntentId id) { + return new EntryForIntentMatcher(id); + } + + @Before + public void setUp() { + manager = new IntentManager(); + flowRuleService = new MockFlowRuleService(); + manager.store = new SimpleIntentStore(); + injectEventDispatcher(manager, new TestEventDispatcher()); + manager.trackerService = new TestIntentTracker(); + manager.flowRuleService = flowRuleService; + manager.coreService = new TestCoreManager(); + service = manager; + extensionService = manager; + + manager.activate(); + service.addListener(listener); + extensionService.registerCompiler(MockIntent.class, compiler); + + assertTrue("store should be empty", + Sets.newHashSet(service.getIntents()).isEmpty()); + assertEquals(0L, flowRuleService.getFlowRuleCount()); + } + + public void verifyState() { + // verify that all intents are parked and the batch operation is unblocked + Set<IntentState> parked = Sets.newHashSet(INSTALLED, WITHDRAWN, FAILED, CORRUPT); + for (Intent i : service.getIntents()) { + IntentState state = service.getIntentState(i.key()); + assertTrue("Intent " + i.id() + " is in invalid state " + state, + parked.contains(state)); + } + //the batch has not yet been removed when we receive the last event + // FIXME: this doesn't guarantee to avoid the race + + //FIXME +// for (int tries = 0; tries < 10; tries++) { +// if (manager.batchService.getPendingOperations().isEmpty()) { +// break; +// } +// delay(10); +// } +// assertTrue("There are still pending batch operations.", +// manager.batchService.getPendingOperations().isEmpty()); + + } + + @After + public void tearDown() { + extensionService.unregisterCompiler(MockIntent.class); + service.removeListener(listener); + manager.deactivate(); + // TODO null the other refs? + } + + @Test + public void submitIntent() { + flowRuleService.setFuture(true); + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.INSTALLED); + Intent intent = new MockIntent(MockIntent.nextId()); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + listener.await(Type.INSTALLED); + assertEquals(1L, service.getIntentCount()); + assertEquals(1L, flowRuleService.getFlowRuleCount()); + verifyState(); + } + + @Test + public void withdrawIntent() { + flowRuleService.setFuture(true); + + listener.setLatch(1, Type.INSTALLED); + Intent intent = new MockIntent(MockIntent.nextId()); + service.submit(intent); + listener.await(Type.INSTALLED); + assertEquals(1L, service.getIntentCount()); + assertEquals(1L, flowRuleService.getFlowRuleCount()); + + listener.setLatch(1, Type.WITHDRAWN); + service.withdraw(intent); + listener.await(Type.WITHDRAWN); + assertEquals(0L, flowRuleService.getFlowRuleCount()); + verifyState(); + } + + @Test + @Ignore("This is disabled because we are seeing intermittent failures on Jenkins") + public void stressSubmitWithdrawUnique() { + flowRuleService.setFuture(true); + + int count = 500; + Intent[] intents = new Intent[count]; + + listener.setLatch(count, Type.WITHDRAWN); + + for (int i = 0; i < count; i++) { + intents[i] = new MockIntent(MockIntent.nextId()); + service.submit(intents[i]); + } + + for (int i = 0; i < count; i++) { + service.withdraw(intents[i]); + } + + listener.await(Type.WITHDRAWN); + assertEquals(0L, flowRuleService.getFlowRuleCount()); + verifyState(); + } + + @Test + public void stressSubmitWithdrawSame() { + flowRuleService.setFuture(true); + + int count = 50; + + Intent intent = new MockIntent(MockIntent.nextId()); + for (int i = 0; i < count; i++) { + service.submit(intent); + service.withdraw(intent); + } + + assertAfter(SUBMIT_TIMEOUT_MS, () -> { + assertEquals(1L, service.getIntentCount()); + assertEquals(0L, flowRuleService.getFlowRuleCount()); + }); + verifyState(); + } + + + /** + * Tests for proper behavior of installation of an intent that triggers + * a compilation error. + */ + @Test + public void errorIntentCompile() { + final TestIntentCompilerError errorCompiler = new TestIntentCompilerError(); + extensionService.registerCompiler(MockIntent.class, errorCompiler); + MockIntent intent = new MockIntent(MockIntent.nextId()); + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.FAILED); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + listener.await(Type.FAILED); + verifyState(); + } + + /** + * Tests handling a future that contains an error as a result of + * installing an intent. + */ + @Ignore("skipping until we fix update ordering problem") + @Test + public void errorIntentInstallFromFlows() { + final Long id = MockIntent.nextId(); + flowRuleService.setFuture(false); + MockIntent intent = new MockIntent(id); + listener.setLatch(1, Type.FAILED); + listener.setLatch(1, Type.INSTALL_REQ); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + listener.await(Type.FAILED); + // FIXME the intent will be moved into INSTALLED immediately which overrides FAILED + // ... the updates come out of order + verifyState(); + } + + /** + * Tests handling a future that contains an unresolvable error as a result of + * installing an intent. + */ + @Test + public void errorIntentInstallNeverTrue() { + final Long id = MockIntent.nextId(); + flowRuleService.setFuture(false); + MockIntent intent = new MockIntent(id); + listener.setLatch(1, Type.CORRUPT); + listener.setLatch(1, Type.INSTALL_REQ); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + // The delay here forces the retry loop in the intent manager to time out + delay(100); + flowRuleService.setFuture(false); + service.withdraw(intent); + listener.await(Type.CORRUPT); + verifyState(); + } + + /** + * Tests that a compiler for a subclass of an intent that already has a + * compiler is automatically added. + */ + @Test + public void intentSubclassCompile() { + class MockIntentSubclass extends MockIntent { + public MockIntentSubclass(Long number) { + super(number); + } + } + flowRuleService.setFuture(true); + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.INSTALLED); + Intent intent = new MockIntentSubclass(MockIntent.nextId()); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + listener.await(Type.INSTALLED); + assertEquals(1L, service.getIntentCount()); + assertEquals(1L, flowRuleService.getFlowRuleCount()); + + final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = + extensionService.getCompilers(); + assertEquals(2, compilers.size()); + assertNotNull(compilers.get(MockIntentSubclass.class)); + assertNotNull(compilers.get(MockIntent.class)); + verifyState(); + } + + /** + * Tests an intent with no compiler. + */ + @Test + public void intentWithoutCompiler() { + class IntentNoCompiler extends Intent { + IntentNoCompiler() { + super(APPID, null, Collections.emptyList(), + Intent.DEFAULT_INTENT_PRIORITY); + } + } + + Intent intent = new IntentNoCompiler(); + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.FAILED); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + listener.await(Type.FAILED); + verifyState(); + } + + /** + * Tests an intent with no installer. + */ + @Test + public void intentWithoutInstaller() { + MockIntent intent = new MockIntent(MockIntent.nextId()); + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.CORRUPT); + service.submit(intent); + listener.await(Type.INSTALL_REQ); + listener.await(Type.CORRUPT); + verifyState(); + } + + /** + * Tests that the intent fetching methods are correct. + */ + @Test + public void testIntentFetching() { + List<Intent> intents; + + flowRuleService.setFuture(true); + + intents = Lists.newArrayList(service.getIntents()); + assertThat(intents, hasSize(0)); + + final MockIntent intent1 = new MockIntent(MockIntent.nextId()); + final MockIntent intent2 = new MockIntent(MockIntent.nextId()); + + listener.setLatch(2, Type.INSTALL_REQ); + listener.setLatch(2, Type.INSTALLED); + service.submit(intent1); + service.submit(intent2); + listener.await(Type.INSTALL_REQ); + listener.await(Type.INSTALL_REQ); + listener.await(Type.INSTALLED); + listener.await(Type.INSTALLED); + + intents = Lists.newArrayList(service.getIntents()); + assertThat(intents, hasSize(2)); + + assertThat(intents, hasIntentWithId(intent1.id())); + assertThat(intents, hasIntentWithId(intent2.id())); + verifyState(); + } + + /** + * Tests that removing all intents results in no flows remaining. + */ + @Test + public void testFlowRemoval() { + List<Intent> intents; + + flowRuleService.setFuture(true); + + intents = Lists.newArrayList(service.getIntents()); + assertThat(intents, hasSize(0)); + + final MockIntent intent1 = new MockIntent(MockIntent.nextId()); + final MockIntent intent2 = new MockIntent(MockIntent.nextId()); + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.INSTALLED); + + service.submit(intent1); + listener.await(Type.INSTALL_REQ); + listener.await(Type.INSTALLED); + + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.INSTALLED); + + service.submit(intent2); + listener.await(Type.INSTALL_REQ); + listener.await(Type.INSTALLED); + + assertThat(listener.getCounts(Type.INSTALLED), is(2)); + assertThat(flowRuleService.getFlowRuleCount(), is(2)); + + listener.setLatch(1, Type.WITHDRAWN); + service.withdraw(intent1); + listener.await(Type.WITHDRAWN); + + listener.setLatch(1, Type.WITHDRAWN); + service.withdraw(intent2); + listener.await(Type.WITHDRAWN); + + assertThat(listener.getCounts(Type.WITHDRAWN), is(2)); + assertThat(flowRuleService.getFlowRuleCount(), is(0)); + } + + /** + * Test failure to install an intent, then succeed on retry via IntentCleanup. + */ + @Test + public void testCorruptCleanup() { + IntentCleanup cleanup = new IntentCleanup(); + cleanup.service = manager; + cleanup.store = manager.store; + cleanup.cfgService = new ComponentConfigAdapter(); + + try { + cleanup.activate(); + + final TestIntentCompilerMultipleFlows errorCompiler = new TestIntentCompilerMultipleFlows(); + extensionService.registerCompiler(MockIntent.class, errorCompiler); + List<Intent> intents; + + flowRuleService.setFuture(false); + + intents = Lists.newArrayList(service.getIntents()); + assertThat(intents, hasSize(0)); + + final MockIntent intent1 = new MockIntent(MockIntent.nextId()); + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.CORRUPT); + listener.setLatch(1, Type.INSTALLED); + + service.submit(intent1); + + listener.await(Type.INSTALL_REQ); + listener.await(Type.CORRUPT); + + flowRuleService.setFuture(true); + + listener.await(Type.INSTALLED); + + assertThat(listener.getCounts(Type.CORRUPT), is(1)); + assertThat(listener.getCounts(Type.INSTALLED), is(1)); + assertEquals(INSTALLED, manager.getIntentState(intent1.key())); + assertThat(flowRuleService.getFlowRuleCount(), is(5)); + } finally { + cleanup.deactivate(); + } + } + + /** + * Test failure to install an intent, and verify retries. + */ + @Test + public void testCorruptRetry() { + IntentCleanup cleanup = new IntentCleanup(); + cleanup.service = manager; + cleanup.store = manager.store; + cleanup.cfgService = new ComponentConfigAdapter(); + cleanup.period = 1_000_000; + cleanup.retryThreshold = 3; + + try { + cleanup.activate(); + + final TestIntentCompilerMultipleFlows errorCompiler = new TestIntentCompilerMultipleFlows(); + extensionService.registerCompiler(MockIntent.class, errorCompiler); + List<Intent> intents; + + flowRuleService.setFuture(false); + + intents = Lists.newArrayList(service.getIntents()); + assertThat(intents, hasSize(0)); + + final MockIntent intent1 = new MockIntent(MockIntent.nextId()); + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(cleanup.retryThreshold, Type.CORRUPT); + listener.setLatch(1, Type.INSTALLED); + + service.submit(intent1); + + listener.await(Type.INSTALL_REQ); + listener.await(Type.CORRUPT); + assertEquals(CORRUPT, manager.getIntentState(intent1.key())); + assertThat(listener.getCounts(Type.CORRUPT), is(cleanup.retryThreshold)); + + } finally { + cleanup.deactivate(); + } + } + + /** + * Tests that an intent that fails installation results in no flows remaining. + */ + @Test + @Ignore("MockFlowRule numbering issue") //test works if run independently + public void testFlowRemovalInstallError() { + final TestIntentCompilerMultipleFlows errorCompiler = new TestIntentCompilerMultipleFlows(); + extensionService.registerCompiler(MockIntent.class, errorCompiler); + List<Intent> intents; + + flowRuleService.setFuture(true); + //FIXME relying on "3" is brittle + flowRuleService.setErrorFlow(3); + + intents = Lists.newArrayList(service.getIntents()); + assertThat(intents, hasSize(0)); + + final MockIntent intent1 = new MockIntent(MockIntent.nextId()); + + listener.setLatch(1, Type.INSTALL_REQ); + listener.setLatch(1, Type.CORRUPT); + + service.submit(intent1); + listener.await(Type.INSTALL_REQ); + listener.await(Type.CORRUPT); + + assertThat(listener.getCounts(Type.CORRUPT), is(1)); + // in this test, there will still be flows abandoned on the data plane + //assertThat(flowRuleService.getFlowRuleCount(), is(0)); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java new file mode 100644 index 00000000..8bd29bf8 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import com.google.common.collect.Sets; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleServiceAdapter; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static org.onosproject.net.flow.FlowRuleOperation.Type.REMOVE; + + +public class MockFlowRuleService extends FlowRuleServiceAdapter { + + final Set<FlowRule> flows = Sets.newHashSet(); + boolean success; + + int errorFlow = -1; + public void setErrorFlow(int errorFlow) { + this.errorFlow = errorFlow; + } + + public void setFuture(boolean success) { + this.success = success; + } + + @Override + public void apply(FlowRuleOperations ops) { + AtomicBoolean thisSuccess = new AtomicBoolean(success); + ops.stages().forEach(stage -> stage.forEach(flow -> { + if (errorFlow == flow.rule().id().value()) { + thisSuccess.set(false); + } else { + switch (flow.type()) { + case ADD: + case MODIFY: //TODO is this the right behavior for modify? + flows.add(flow.rule()); + break; + case REMOVE: + flows.remove(flow.rule()); + break; + default: + break; + } + } + })); + if (thisSuccess.get()) { + ops.callback().onSuccess(ops); + } else { + ops.callback().onError(ops); + } + } + + @Override + public int getFlowRuleCount() { + return flows.size(); + } + + @Override + public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) { + return flows.stream() + .filter(flow -> flow.deviceId().equals(deviceId)) + .map(DefaultFlowEntry::new) + .collect(Collectors.toList()); + } + + @Override + public void applyFlowRules(FlowRule... flowRules) { + for (FlowRule flow : flowRules) { + flows.add(flow); + } + } + + @Override + public void removeFlowRules(FlowRule... flowRules) { + for (FlowRule flow : flowRules) { + flows.remove(flow); + } + } + + @Override + public Iterable<FlowRule> getFlowRulesById(ApplicationId id) { + return flows.stream() + .filter(flow -> flow.appId() == id.id()) + .collect(Collectors.toList()); + } + + @Override + public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) { + return flows.stream() + .filter(flow -> flow.appId() == appId.id() && flow.groupId().id() == groupId) + .collect(Collectors.toList()); + } +} + diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java new file mode 100644 index 00000000..58fa1292 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java @@ -0,0 +1,326 @@ +/* + * Copyright 2014-2015 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.net.intent.impl; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.junit.TestUtils; +import org.onlab.junit.TestUtils.TestUtilsException; +import org.onosproject.core.IdGenerator; +import org.onosproject.event.Event; +import org.onosproject.net.Device; +import org.onosproject.net.Link; +import org.onosproject.net.NetworkResource; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.resource.link.LinkResourceEvent; +import org.onosproject.net.resource.link.LinkResourceListener; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyListener; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +import static org.easymock.EasyMock.createMock; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.device; +import static org.onosproject.net.NetTestTools.link; + +/** + * Tests for the objective tracker. + */ +public class ObjectiveTrackerTest { + private static final int WAIT_TIMEOUT_SECONDS = 2; + private Topology topology; + private ObjectiveTracker tracker; + private TestTopologyChangeDelegate delegate; + private List<Event> reasons; + private TopologyListener listener; + private DeviceListener deviceListener; + private LinkResourceListener linkResourceListener; + private IdGenerator mockGenerator; + + /** + * Initialization shared by all test cases. + * + * @throws TestUtilsException if any filed look ups fail + */ + @Before + public void setUp() throws TestUtilsException { + topology = createMock(Topology.class); + tracker = new ObjectiveTracker(); + delegate = new TestTopologyChangeDelegate(); + tracker.setDelegate(delegate); + reasons = new LinkedList<>(); + listener = TestUtils.getField(tracker, "listener"); + deviceListener = TestUtils.getField(tracker, "deviceListener"); + linkResourceListener = TestUtils.getField(tracker, "linkResourceListener"); + mockGenerator = new MockIdGenerator(); + Intent.bindIdGenerator(mockGenerator); + } + + /** + * Code to clean up shared by all test case. + */ + @After + public void tearDown() { + tracker.unsetDelegate(delegate); + Intent.unbindIdGenerator(mockGenerator); + } + + /** + * Topology change delegate mock that tracks the events coming into it + * and saves them. It provides a latch so that tests can wait for events + * to be generated. + */ + static class TestTopologyChangeDelegate implements TopologyChangeDelegate { + + CountDownLatch latch = new CountDownLatch(1); + List<Key> intentIdsFromEvent; + boolean compileAllFailedFromEvent; + + @Override + public void triggerCompile(Iterable<Key> intentKeys, + boolean compileAllFailed) { + intentIdsFromEvent = Lists.newArrayList(intentKeys); + compileAllFailedFromEvent = compileAllFailed; + latch.countDown(); + } + } + + /** + * Tests an event with no associated reasons. + * + * @throws InterruptedException if the latch wait fails. + */ + @Test + public void testEventNoReasons() throws InterruptedException { + final TopologyEvent event = new TopologyEvent( + TopologyEvent.Type.TOPOLOGY_CHANGED, + topology, + null); + + listener.event(event); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(0)); + assertThat(delegate.compileAllFailedFromEvent, is(true)); + } + + /** + * Tests an event for a link down where none of the reasons match + * currently installed intents. + * + * @throws InterruptedException if the latch wait fails. + */ + @Test + public void testEventLinkDownNoMatches() throws InterruptedException { + final Link link = link("src", 1, "dst", 2); + final LinkEvent linkEvent = new LinkEvent(LinkEvent.Type.LINK_REMOVED, link); + reasons.add(linkEvent); + + final TopologyEvent event = new TopologyEvent( + TopologyEvent.Type.TOPOLOGY_CHANGED, + topology, + reasons); + + listener.event(event); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(0)); + assertThat(delegate.compileAllFailedFromEvent, is(false)); + } + + /** + * Tests an event for a link being added. + * + * @throws InterruptedException if the latch wait fails. + */ + @Test + public void testEventLinkAdded() throws InterruptedException { + final Link link = link("src", 1, "dst", 2); + final LinkEvent linkEvent = new LinkEvent(LinkEvent.Type.LINK_ADDED, link); + reasons.add(linkEvent); + + final TopologyEvent event = new TopologyEvent( + TopologyEvent.Type.TOPOLOGY_CHANGED, + topology, + reasons); + + listener.event(event); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(0)); + assertThat(delegate.compileAllFailedFromEvent, is(true)); + } + + /** + * Tests an event for a link down where the link matches existing intents. + * + * @throws InterruptedException if the latch wait fails. + */ + @Test + public void testEventLinkDownMatch() throws Exception { + final Link link = link("src", 1, "dst", 2); + final LinkEvent linkEvent = new LinkEvent(LinkEvent.Type.LINK_REMOVED, link); + reasons.add(linkEvent); + + final TopologyEvent event = new TopologyEvent( + TopologyEvent.Type.TOPOLOGY_CHANGED, + topology, + reasons); + + final Key key = Key.of(0x333L, APP_ID); + Collection<NetworkResource> resources = ImmutableSet.of(link); + tracker.addTrackedResources(key, resources); + + listener.event(event); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(1)); + assertThat(delegate.compileAllFailedFromEvent, is(false)); + assertThat(delegate.intentIdsFromEvent.get(0).toString(), + equalTo("0x333")); + } + + /** + * Tests a resource available event. + * + * @throws InterruptedException if the latch wait fails. + */ + @Test + public void testResourceEvent() throws Exception { + LinkResourceEvent event = new LinkResourceEvent( + LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE, + new HashSet<>()); + linkResourceListener.event(event); + + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(0)); + assertThat(delegate.compileAllFailedFromEvent, is(true)); + } + + /** + * Tests an event for a host becoming available that matches an intent. + * + * @throws InterruptedException if the latch wait fails. + */ + + @Test + public void testEventHostAvailableMatch() throws Exception { + final Device host = device("host1"); + + final DeviceEvent deviceEvent = + new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, host); + reasons.add(deviceEvent); + + final Key key = Key.of(0x333L, APP_ID); + Collection<NetworkResource> resources = ImmutableSet.of(host.id()); + tracker.addTrackedResources(key, resources); + + deviceListener.event(deviceEvent); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(1)); + assertThat(delegate.compileAllFailedFromEvent, is(true)); + assertThat(delegate.intentIdsFromEvent.get(0).toString(), + equalTo("0x333")); + } + + /** + * Tests an event for a host becoming unavailable that matches an intent. + * + * @throws InterruptedException if the latch wait fails. + */ + + @Test + public void testEventHostUnavailableMatch() throws Exception { + final Device host = device("host1"); + + final DeviceEvent deviceEvent = + new DeviceEvent(DeviceEvent.Type.DEVICE_REMOVED, host); + reasons.add(deviceEvent); + + final Key key = Key.of(0x333L, APP_ID); + Collection<NetworkResource> resources = ImmutableSet.of(host.id()); + tracker.addTrackedResources(key, resources); + + deviceListener.event(deviceEvent); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(1)); + assertThat(delegate.compileAllFailedFromEvent, is(false)); + assertThat(delegate.intentIdsFromEvent.get(0).toString(), + equalTo("0x333")); + } + + /** + * Tests an event for a host becoming available that matches an intent. + * + * @throws InterruptedException if the latch wait fails. + */ + + @Test + public void testEventHostAvailableNoMatch() throws Exception { + final Device host = device("host1"); + + final DeviceEvent deviceEvent = + new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, host); + reasons.add(deviceEvent); + + deviceListener.event(deviceEvent); + assertThat( + delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS), + is(true)); + + assertThat(delegate.intentIdsFromEvent, hasSize(0)); + assertThat(delegate.compileAllFailedFromEvent, is(true)); + } + + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java new file mode 100644 index 00000000..5588904d --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java @@ -0,0 +1,167 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.core.ApplicationId; +import org.onosproject.TestApplicationId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.host.HostService; +import org.onosproject.net.intent.AbstractIntentTest; +import org.onosproject.net.intent.HostToHostIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentTestsMocks; +import org.onosproject.net.intent.PathIntent; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; + +import java.util.List; + +import static org.easymock.EasyMock.*; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.NetTestTools.hid; +import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath; + +/** + * Unit tests for the HostToHost intent compiler. + */ +public class HostToHostIntentCompilerTest extends AbstractIntentTest { + private static final String HOST_ONE_MAC = "00:00:00:00:00:01"; + private static final String HOST_TWO_MAC = "00:00:00:00:00:02"; + private static final String HOST_ONE_VLAN = "-1"; + private static final String HOST_TWO_VLAN = "-1"; + private static final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN; + private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN; + + private static final ApplicationId APPID = new TestApplicationId("foo"); + + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); + + private HostId hostOneId = HostId.hostId(HOST_ONE); + private HostId hostTwoId = HostId.hostId(HOST_TWO); + private HostService mockHostService; + + @Before + public void setUp() throws Exception { + super.setUp(); + Host hostOne = createMock(Host.class); + expect(hostOne.mac()).andReturn(new MacAddress(HOST_ONE_MAC.getBytes())).anyTimes(); + expect(hostOne.vlan()).andReturn(VlanId.vlanId()).anyTimes(); + replay(hostOne); + + Host hostTwo = createMock(Host.class); + expect(hostTwo.mac()).andReturn(new MacAddress(HOST_TWO_MAC.getBytes())).anyTimes(); + expect(hostTwo.vlan()).andReturn(VlanId.vlanId()).anyTimes(); + replay(hostTwo); + + mockHostService = createMock(HostService.class); + expect(mockHostService.getHost(eq(hostOneId))).andReturn(hostOne).anyTimes(); + expect(mockHostService.getHost(eq(hostTwoId))).andReturn(hostTwo).anyTimes(); + replay(mockHostService); + } + + /** + * Creates a HostToHost intent based on two host Ids. + * + * @param oneIdString string for host one id + * @param twoIdString string for host two id + * @return HostToHostIntent for the two hosts + */ + private HostToHostIntent makeIntent(String oneIdString, String twoIdString) { + return HostToHostIntent.builder() + .appId(APPID) + .one(hid(oneIdString)) + .two(hid(twoIdString)) + .selector(selector) + .treatment(treatment) + .build(); + } + + /** + * Creates a compiler for HostToHost intents. + * + * @param hops string array describing the path hops to use when compiling + * @return HostToHost intent compiler + */ + private HostToHostIntentCompiler makeCompiler(String[] hops) { + HostToHostIntentCompiler compiler = + new HostToHostIntentCompiler(); + compiler.pathService = new IntentTestsMocks.MockPathService(hops); + compiler.hostService = mockHostService; + return compiler; + } + + + /** + * Tests a pair of hosts with 8 hops between them. + */ + @Test + public void testSingleLongPathCompilation() { + + HostToHostIntent intent = makeIntent(HOST_ONE, + HOST_TWO); + assertThat(intent, is(notNullValue())); + + String[] hops = {HOST_ONE, "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", HOST_TWO}; + HostToHostIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(Matchers.notNullValue())); + assertThat(result, hasSize(2)); + Intent forwardResultIntent = result.get(0); + assertThat(forwardResultIntent instanceof PathIntent, is(true)); + Intent reverseResultIntent = result.get(1); + assertThat(reverseResultIntent instanceof PathIntent, is(true)); + + if (forwardResultIntent instanceof PathIntent) { + PathIntent forwardPathIntent = (PathIntent) forwardResultIntent; + assertThat(forwardPathIntent.path().links(), hasSize(9)); + assertThat(forwardPathIntent.path().links(), linksHasPath(HOST_ONE, "h1")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h1", "h2")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h2", "h3")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h3", "h4")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h4", "h5")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h5", "h6")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h6", "h7")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h7", "h8")); + assertThat(forwardPathIntent.path().links(), linksHasPath("h8", HOST_TWO)); + } + + if (reverseResultIntent instanceof PathIntent) { + PathIntent reversePathIntent = (PathIntent) reverseResultIntent; + assertThat(reversePathIntent.path().links(), hasSize(9)); + assertThat(reversePathIntent.path().links(), linksHasPath("h1", HOST_ONE)); + assertThat(reversePathIntent.path().links(), linksHasPath("h2", "h1")); + assertThat(reversePathIntent.path().links(), linksHasPath("h3", "h2")); + assertThat(reversePathIntent.path().links(), linksHasPath("h4", "h3")); + assertThat(reversePathIntent.path().links(), linksHasPath("h5", "h4")); + assertThat(reversePathIntent.path().links(), linksHasPath("h6", "h5")); + assertThat(reversePathIntent.path().links(), linksHasPath("h7", "h6")); + assertThat(reversePathIntent.path().links(), linksHasPath("h8", "h7")); + assertThat(reversePathIntent.path().links(), linksHasPath(HOST_TWO, "h8")); + } + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java new file mode 100644 index 00000000..c5fa3719 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import com.google.common.collect.ImmutableSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.LinkCollectionIntent; +import org.onosproject.net.intent.MockIdGenerator; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.PID; +import static org.onosproject.net.NetTestTools.connectPoint; + +public class LinkCollectionIntentCompilerTest { + + private final ApplicationId appId = new TestApplicationId("test"); + + private final ConnectPoint d1p1 = connectPoint("s1", 0); + private final ConnectPoint d2p0 = connectPoint("s2", 0); + private final ConnectPoint d2p1 = connectPoint("s2", 1); + private final ConnectPoint d3p1 = connectPoint("s3", 1); + private final ConnectPoint d3p0 = connectPoint("s3", 10); + private final ConnectPoint d1p0 = connectPoint("s1", 10); + + private final Set<Link> links = ImmutableSet.of( + new DefaultLink(PID, d1p1, d2p0, DIRECT), + new DefaultLink(PID, d2p1, d3p1, DIRECT), + new DefaultLink(PID, d1p1, d3p1, DIRECT)); + + private final TrafficSelector selector = DefaultTrafficSelector.builder().build(); + private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + private CoreService coreService; + private IntentExtensionService intentExtensionService; + private IdGenerator idGenerator = new MockIdGenerator(); + + private LinkCollectionIntent intent; + + private LinkCollectionIntentCompiler sut; + + @Before + public void setUp() { + sut = new LinkCollectionIntentCompiler(); + coreService = createMock(CoreService.class); + expect(coreService.registerApplication("org.onosproject.net.intent")) + .andReturn(appId); + sut.coreService = coreService; + + Intent.bindIdGenerator(idGenerator); + + intent = LinkCollectionIntent.builder() + .appId(APP_ID) + .selector(selector) + .treatment(treatment) + .links(links) + .ingressPoints(ImmutableSet.of(d1p1)) + .egressPoints(ImmutableSet.of(d3p1)) + .build(); + intentExtensionService = createMock(IntentExtensionService.class); + intentExtensionService.registerCompiler(LinkCollectionIntent.class, sut); + intentExtensionService.unregisterCompiler(LinkCollectionIntent.class); + sut.intentManager = intentExtensionService; + + replay(coreService, intentExtensionService); + } + + @After + public void tearDown() { + Intent.unbindIdGenerator(idGenerator); + } + + @Test + public void testCompile() { + sut.activate(); + + List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet()); + assertThat(compiled, hasSize(1)); + + Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules(); + assertThat(rules, hasSize(links.size())); + + // if not found, get() raises an exception + FlowRule rule1 = rules.stream() + .filter(rule -> rule.deviceId().equals(d1p0.deviceId())) + .findFirst() + .get(); + assertThat(rule1.selector(), is( + DefaultTrafficSelector.builder(intent.selector()).matchInPort(d1p1.port()).build() + )); + assertThat(rule1.treatment(), is( + DefaultTrafficTreatment.builder(intent.treatment()).setOutput(d1p1.port()).build() + )); + assertThat(rule1.priority(), is(intent.priority())); + + FlowRule rule2 = rules.stream() + .filter(rule -> rule.deviceId().equals(d2p0.deviceId())) + .findFirst() + .get(); + assertThat(rule2.selector(), is( + DefaultTrafficSelector.builder(intent.selector()).matchInPort(d2p0.port()).build() + )); + assertThat(rule2.treatment(), is( + DefaultTrafficTreatment.builder().setOutput(d2p1.port()).build() + )); + assertThat(rule2.priority(), is(intent.priority())); + + FlowRule rule3 = rules.stream() + .filter(rule -> rule.deviceId().equals(d3p0.deviceId())) + .findFirst() + .get(); + assertThat(rule3.selector(), is( + DefaultTrafficSelector.builder(intent.selector()).matchInPort(d3p1.port()).build() + )); + assertThat(rule3.treatment(), is( + DefaultTrafficTreatment.builder().setOutput(d3p1.port()).build() + )); + assertThat(rule3.priority(), is(intent.priority())); + + sut.deactivate(); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java new file mode 100644 index 00000000..76b26f46 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java @@ -0,0 +1,188 @@ +package org.onosproject.net.intent.impl.compiler; + +import java.util.List; +import java.util.Optional; + +import org.hamcrest.Matchers; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +import org.onlab.packet.MplsLabel; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.AbstractIntentTest; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentTestsMocks; +import org.onosproject.net.intent.MplsIntent; +import org.onosproject.net.intent.MplsPathIntent; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.connectPoint; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath; + +/** + * Unit tests for the HostToHost intent compiler. + */ +public class MplsIntentCompilerTest extends AbstractIntentTest { + + private static final ApplicationId APPID = new TestApplicationId("foo"); + + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); + + /** + * Creates a PointToPoint intent based on ingress and egress device Ids. + * + * @param ingressIdString string for id of ingress device + * @param egressIdString string for id of egress device + * @return PointToPointIntent for the two devices + */ + private MplsIntent makeIntent(String ingressIdString, Optional<MplsLabel> ingressLabel, + String egressIdString, Optional<MplsLabel> egressLabel) { + + return MplsIntent.builder() + .appId(APPID) + .selector(selector) + .treatment(treatment) + .ingressPoint(connectPoint(ingressIdString, 1)) + .ingressLabel(ingressLabel) + .egressPoint(connectPoint(egressIdString, 1)) + .egressLabel(egressLabel).build(); + } + /** + * Creates a compiler for HostToHost intents. + * + * @param hops string array describing the path hops to use when compiling + * @return HostToHost intent compiler + */ + private MplsIntentCompiler makeCompiler(String[] hops) { + MplsIntentCompiler compiler = + new MplsIntentCompiler(); + compiler.pathService = new IntentTestsMocks.MockPathService(hops); + return compiler; + } + + + /** + * Tests a pair of devices in an 8 hop path, forward direction. + */ + @Test + public void testForwardPathCompilation() { + Optional<MplsLabel> ingressLabel = Optional.of(MplsLabel.mplsLabel(10)); + Optional<MplsLabel> egressLabel = Optional.of(MplsLabel.mplsLabel(20)); + + MplsIntent intent = makeIntent("d1", ingressLabel, "d8", egressLabel); + assertThat(intent, is(notNullValue())); + + String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"}; + MplsIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(Matchers.notNullValue())); + assertThat(result, hasSize(1)); + Intent forwardResultIntent = result.get(0); + assertThat(forwardResultIntent instanceof MplsPathIntent, is(true)); + + // if statement suppresses static analysis warnings about unchecked cast + if (forwardResultIntent instanceof MplsPathIntent) { + MplsPathIntent forwardPathIntent = (MplsPathIntent) forwardResultIntent; + // 7 links for the hops, plus one default lnk on ingress and egress + assertThat(forwardPathIntent.path().links(), hasSize(hops.length + 1)); + assertThat(forwardPathIntent.path().links(), linksHasPath("d1", "d2")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d2", "d3")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d3", "d4")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d4", "d5")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d5", "d6")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d6", "d7")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d7", "d8")); + assertEquals(forwardPathIntent.egressLabel(), egressLabel); + assertEquals(forwardPathIntent.ingressLabel(), ingressLabel); + } + } + + /** + * Tests a pair of devices in an 8 hop path, forward direction. + */ + @Test + public void testReversePathCompilation() { + Optional<MplsLabel> ingressLabel = Optional.of(MplsLabel.mplsLabel(10)); + Optional<MplsLabel> egressLabel = Optional.of(MplsLabel.mplsLabel(20)); + + MplsIntent intent = makeIntent("d8", ingressLabel, "d1", egressLabel); + assertThat(intent, is(notNullValue())); + + String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"}; + MplsIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(Matchers.notNullValue())); + assertThat(result, hasSize(1)); + Intent reverseResultIntent = result.get(0); + assertThat(reverseResultIntent instanceof MplsPathIntent, is(true)); + + // if statement suppresses static analysis warnings about unchecked cast + if (reverseResultIntent instanceof MplsPathIntent) { + MplsPathIntent reversePathIntent = (MplsPathIntent) reverseResultIntent; + assertThat(reversePathIntent.path().links(), hasSize(hops.length + 1)); + assertThat(reversePathIntent.path().links(), linksHasPath("d2", "d1")); + assertThat(reversePathIntent.path().links(), linksHasPath("d3", "d2")); + assertThat(reversePathIntent.path().links(), linksHasPath("d4", "d3")); + assertThat(reversePathIntent.path().links(), linksHasPath("d5", "d4")); + assertThat(reversePathIntent.path().links(), linksHasPath("d6", "d5")); + assertThat(reversePathIntent.path().links(), linksHasPath("d7", "d6")); + assertThat(reversePathIntent.path().links(), linksHasPath("d8", "d7")); + assertEquals(reversePathIntent.egressLabel(), egressLabel); + assertEquals(reversePathIntent.ingressLabel(), ingressLabel); + } + } + + /** + * Tests compilation of the intent which designates two different ports on the same switch. + */ + @Test + public void testSameSwitchDifferentPortsIntentCompilation() { + ConnectPoint src = new ConnectPoint(deviceId("1"), portNumber(1)); + ConnectPoint dst = new ConnectPoint(deviceId("1"), portNumber(2)); + MplsIntent intent = MplsIntent.builder() + .appId(APP_ID) + .selector(selector) + .treatment(treatment) + .ingressPoint(src) + .ingressLabel(Optional.empty()) + .egressPoint(dst) + .egressLabel(Optional.empty()) + .build(); + + String[] hops = {"1"}; + MplsIntentCompiler sut = makeCompiler(hops); + + List<Intent> compiled = sut.compile(intent, null, null); + + assertThat(compiled, hasSize(1)); + assertThat(compiled.get(0), is(instanceOf(MplsPathIntent.class))); + Path path = ((MplsPathIntent) compiled.get(0)).path(); + + assertThat(path.links(), hasSize(2)); + Link firstLink = path.links().get(0); + assertThat(firstLink, is(createEdgeLink(src, true))); + Link secondLink = path.links().get(1); + assertThat(secondLink, is(createEdgeLink(dst, false))); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java new file mode 100644 index 00000000..771a9883 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.MplsLabel; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.IntentTestsMocks; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.net.intent.MplsPathIntent; +import org.onosproject.store.trivial.SimpleLinkStore; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.PID; +import static org.onosproject.net.NetTestTools.connectPoint; + +public class MplsPathIntentCompilerTest { + + private final ApplicationId appId = new TestApplicationId("test"); + + private final ConnectPoint d1p1 = connectPoint("s1", 0); + private final ConnectPoint d2p0 = connectPoint("s2", 0); + private final ConnectPoint d2p1 = connectPoint("s2", 1); + private final ConnectPoint d3p1 = connectPoint("s3", 1); + + private final TrafficSelector selector = DefaultTrafficSelector.builder().build(); + private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + + private final Optional<MplsLabel> ingressLabel = + Optional.of(MplsLabel.mplsLabel(10)); + private final Optional<MplsLabel> egressLabel = + Optional.of(MplsLabel.mplsLabel(20)); + + private final List<Link> links = Arrays.asList( + new DefaultLink(PID, d1p1, d2p0, DIRECT), + new DefaultLink(PID, d2p1, d3p1, DIRECT) + ); + + private IdGenerator idGenerator = new MockIdGenerator(); + + private final int hops = links.size() - 1; + private MplsPathIntent intent; + private MplsPathIntentCompiler sut; + + @Before + public void setUp() { + sut = new MplsPathIntentCompiler(); + CoreService coreService = createMock(CoreService.class); + expect(coreService.registerApplication("org.onosproject.net.intent")) + .andReturn(appId); + sut.coreService = coreService; + sut.linkStore = new SimpleLinkStore(); + sut.resourceService = new IntentTestsMocks.MockResourceService(); + + Intent.bindIdGenerator(idGenerator); + + intent = MplsPathIntent.builder() + .appId(APP_ID) + .selector(selector) + .treatment(treatment) + .path(new DefaultPath(PID, links, hops)) + .ingressLabel(ingressLabel) + .egressLabel(egressLabel) + .priority(55) + .build(); + + IntentExtensionService intentExtensionService = createMock(IntentExtensionService.class); + intentExtensionService.registerCompiler(MplsPathIntent.class, sut); + intentExtensionService.unregisterCompiler(MplsPathIntent.class); + sut.intentExtensionService = intentExtensionService; + + replay(coreService, intentExtensionService); + } + + @After + public void tearDown() { + Intent.unbindIdGenerator(idGenerator); + } + + @Test + public void testCompile() { + sut.activate(); + + List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet()); + assertThat(compiled, hasSize(1)); + + Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules(); + assertThat(rules, hasSize(1)); + + FlowRule rule = rules.stream() + .filter(x -> x.deviceId().equals(d2p0.deviceId())) + .findFirst() + .get(); + assertThat(rule.deviceId(), is(d2p0.deviceId())); + + sut.deactivate(); + + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java new file mode 100644 index 00000000..eb7a3936 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java @@ -0,0 +1,276 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.ElementId; +import org.onosproject.net.Path; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.AbstractIntentTest; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentTestsMocks; +import org.onosproject.net.intent.LinkCollectionIntent; +import org.onosproject.net.intent.MultiPointToSinglePointIntent; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.PathService; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.NetTestTools.connectPoint; +import static org.onosproject.net.NetTestTools.createPath; +import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath; + +/** + * Unit tests for the MultiPointToSinglePoint intent compiler. + */ +public class MultiPointToSinglePointIntentCompilerTest extends AbstractIntentTest { + + private static final ApplicationId APPID = new TestApplicationId("foo"); + + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); + + /** + * Mock path service for creating paths within the test. + */ + private static class MockPathService implements PathService { + + final String[] pathHops; + + /** + * Constructor that provides a set of hops to mock. + * + * @param pathHops path hops to mock + */ + MockPathService(String[] pathHops) { + this.pathHops = pathHops; + } + + @Override + public Set<Path> getPaths(ElementId src, ElementId dst) { + Set<Path> result = new HashSet<>(); + + String[] allHops = new String[pathHops.length + 1]; + allHops[0] = src.toString(); + if (pathHops.length != 0) { + System.arraycopy(pathHops, 0, allHops, 1, pathHops.length); + } + result.add(createPath(allHops)); + + return result; + } + + @Override + public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) { + return null; + } + } + + /** + * Mocks the device service so that a device appears available in the test. + */ + private static class MockDeviceService extends DeviceServiceAdapter { + @Override + public boolean isAvailable(DeviceId deviceId) { + return true; + } + } + + /** + * Creates a MultiPointToSinglePoint intent for a group of ingress points + * and an egress point. + * + * @param ingressIds array of ingress device ids + * @param egressId device id of the egress point + * @return MultiPointToSinglePoint intent + */ + private MultiPointToSinglePointIntent makeIntent(String[] ingressIds, String egressId) { + Set<ConnectPoint> ingressPoints = new HashSet<>(); + ConnectPoint egressPoint = connectPoint(egressId, 2); + + for (String ingressId : ingressIds) { + ingressPoints.add(connectPoint(ingressId, 1)); + } + + return MultiPointToSinglePointIntent.builder() + .appId(APPID) + .selector(selector) + .treatment(treatment) + .ingressPoints(ingressPoints) + .egressPoint(egressPoint) + .build(); + } + + /** + * Creates a compiler for MultiPointToSinglePoint intents. + * + * @param hops hops to use while computing paths for this intent + * @return MultiPointToSinglePoint intent + */ + private MultiPointToSinglePointIntentCompiler makeCompiler(String[] hops) { + MultiPointToSinglePointIntentCompiler compiler = + new MultiPointToSinglePointIntentCompiler(); + compiler.pathService = new MockPathService(hops); + compiler.deviceService = new MockDeviceService(); + return compiler; + } + + /** + * Tests a single ingress point with 8 hops to its egress point. + */ + @Test + public void testSingleLongPathCompilation() { + + String[] ingress = {"ingress"}; + String egress = "egress"; + + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); + assertThat(intent, is(notNullValue())); + + String[] hops = {"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", + egress}; + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(Matchers.notNullValue())); + assertThat(result, hasSize(1)); + Intent resultIntent = result.get(0); + assertThat(resultIntent instanceof LinkCollectionIntent, is(true)); + + if (resultIntent instanceof LinkCollectionIntent) { + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; + assertThat(linkIntent.links(), hasSize(9)); + assertThat(linkIntent.links(), linksHasPath("ingress", "h1")); + assertThat(linkIntent.links(), linksHasPath("h1", "h2")); + assertThat(linkIntent.links(), linksHasPath("h2", "h3")); + assertThat(linkIntent.links(), linksHasPath("h4", "h5")); + assertThat(linkIntent.links(), linksHasPath("h5", "h6")); + assertThat(linkIntent.links(), linksHasPath("h7", "h8")); + assertThat(linkIntent.links(), linksHasPath("h8", "egress")); + } + } + + /** + * Tests a simple topology where two ingress points share some path segments + * and some path segments are not shared. + */ + @Test + public void testTwoIngressCompilation() { + String[] ingress = {"ingress1", "ingress2"}; + String egress = "egress"; + + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); + assertThat(intent, is(notNullValue())); + + final String[] hops = {"inner1", "inner2", egress}; + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(notNullValue())); + assertThat(result, hasSize(1)); + Intent resultIntent = result.get(0); + assertThat(resultIntent instanceof LinkCollectionIntent, is(true)); + + if (resultIntent instanceof LinkCollectionIntent) { + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; + assertThat(linkIntent.links(), hasSize(4)); + assertThat(linkIntent.links(), linksHasPath("ingress1", "inner1")); + assertThat(linkIntent.links(), linksHasPath("ingress2", "inner1")); + assertThat(linkIntent.links(), linksHasPath("inner1", "inner2")); + assertThat(linkIntent.links(), linksHasPath("inner2", "egress")); + } + } + + /** + * Tests a large number of ingress points that share a common path to the + * egress point. + */ + @Test + public void testMultiIngressCompilation() { + String[] ingress = {"i1", "i2", "i3", "i4", "i5", + "i6", "i7", "i8", "i9", "i10"}; + String egress = "e"; + + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); + assertThat(intent, is(notNullValue())); + + final String[] hops = {"n1", egress}; + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(notNullValue())); + assertThat(result, hasSize(1)); + Intent resultIntent = result.get(0); + assertThat(resultIntent instanceof LinkCollectionIntent, is(true)); + + if (resultIntent instanceof LinkCollectionIntent) { + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; + assertThat(linkIntent.links(), hasSize(ingress.length + 1)); + for (String ingressToCheck : ingress) { + assertThat(linkIntent.links(), + linksHasPath(ingressToCheck, + "n1")); + } + assertThat(linkIntent.links(), linksHasPath("n1", egress)); + } + } + + /** + * Tests ingress and egress on the same device. + */ + @Test + public void testSameDeviceCompilation() { + String[] ingress = {"i1", "i2"}; + String egress = "i1"; + + MultiPointToSinglePointIntent intent = makeIntent(ingress, egress); + assertThat(intent, is(notNullValue())); + + final String[] hops = {"i1", "i2"}; + MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops); + assertThat(compiler, is(notNullValue())); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(notNullValue())); + assertThat(result, hasSize(1)); + Intent resultIntent = result.get(0); + assertThat(resultIntent, instanceOf(LinkCollectionIntent.class)); + + if (resultIntent instanceof LinkCollectionIntent) { + LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent; + assertThat(linkIntent.links(), hasSize(ingress.length)); + + assertThat(linkIntent.links(), linksHasPath("i2", "i1")); + } + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java new file mode 100644 index 00000000..2f40b37a --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.OchSignalType; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.IntentTestsMocks; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.net.intent.OpticalPathIntent; +import org.onosproject.net.provider.ProviderId; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.NetTestTools.PID; +import static org.onosproject.net.NetTestTools.connectPoint; +import static org.onosproject.net.NetTestTools.createLambda; + +public class OpticalPathIntentCompilerTest { + + private CoreService coreService; + private IntentExtensionService intentExtensionService; + private final IdGenerator idGenerator = new MockIdGenerator(); + private OpticalPathIntentCompiler sut; + + private final TrafficSelector selector = DefaultTrafficSelector.builder().build(); + private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + private final ApplicationId appId = new TestApplicationId("test"); + private final ProviderId pid = new ProviderId("of", "test"); + private final ConnectPoint d1p1 = connectPoint("s1", 0); + private final ConnectPoint d2p0 = connectPoint("s2", 0); + private final ConnectPoint d2p1 = connectPoint("s2", 1); + private final ConnectPoint d3p1 = connectPoint("s3", 1); + private final ConnectPoint d3p0 = connectPoint("s3", 10); + private final ConnectPoint d1p0 = connectPoint("s1", 10); + + private final List<Link> links = Arrays.asList( + new DefaultLink(PID, d1p1, d2p0, DIRECT), + new DefaultLink(PID, d2p1, d3p1, DIRECT) + ); + private final int hops = links.size() + 1; + private OpticalPathIntent intent; + + @Before + public void setUp() { + sut = new OpticalPathIntentCompiler(); + coreService = createMock(CoreService.class); + expect(coreService.registerApplication("org.onosproject.net.intent")) + .andReturn(appId); + sut.coreService = coreService; + + Intent.bindIdGenerator(idGenerator); + + intent = OpticalPathIntent.builder() + .appId(appId) + .src(d1p1) + .dst(d3p1) + .path(new DefaultPath(PID, links, hops)) + .lambda(createLambda()) + .signalType(OchSignalType.FIXED_GRID) + .build(); + intentExtensionService = createMock(IntentExtensionService.class); + intentExtensionService.registerCompiler(OpticalPathIntent.class, sut); + intentExtensionService.unregisterCompiler(OpticalPathIntent.class); + sut.intentManager = intentExtensionService; + sut.resourceService = new IntentTestsMocks.MockResourceService(); + + replay(coreService, intentExtensionService); + } + + @After + public void tearDown() { + Intent.unbindIdGenerator(idGenerator); + } + + @Test + public void testCompiler() { + sut.activate(); + + List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet()); + assertThat(compiled, hasSize(1)); + + Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules(); + rules.stream() + .filter(x -> x.deviceId().equals(d1p1.deviceId())) + .findFirst() + .get(); + + rules.stream() + .filter(x -> x.deviceId().equals(d2p1.deviceId())) + .findFirst() + .get(); + + rules.stream() + .filter(x -> x.deviceId().equals(d3p1.deviceId())) + .findFirst() + .get(); + + rules.forEach(rule -> assertEquals("FlowRule priority is incorrect", + intent.priority(), rule.priority())); + + sut.deactivate(); + } + + //TODO test bidirectional optical paths and verify rules + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java new file mode 100644 index 00000000..f07bf42c --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2015 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.net.intent.impl.compiler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.FlowRuleIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentExtensionService; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.net.intent.PathIntent; +import org.onosproject.net.provider.ProviderId; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.PID; +import static org.onosproject.net.NetTestTools.connectPoint; + +/** + * Unit tests for PathIntentCompiler. + */ +public class PathIntentCompilerTest { + + private CoreService coreService; + private IntentExtensionService intentExtensionService; + private IdGenerator idGenerator = new MockIdGenerator(); + private PathIntentCompiler sut; + + private final TrafficSelector selector = DefaultTrafficSelector.builder().build(); + private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); + private final ApplicationId appId = new TestApplicationId("test"); + private final ProviderId pid = new ProviderId("of", "test"); + private final ConnectPoint d1p1 = connectPoint("s1", 0); + private final ConnectPoint d2p0 = connectPoint("s2", 0); + private final ConnectPoint d2p1 = connectPoint("s2", 1); + private final ConnectPoint d3p1 = connectPoint("s3", 1); + private final ConnectPoint d3p0 = connectPoint("s3", 10); + private final ConnectPoint d1p0 = connectPoint("s1", 10); + private static final int PRIORITY = 555; + + private final List<Link> links = Arrays.asList( + createEdgeLink(d1p0, true), + new DefaultLink(PID, d1p1, d2p0, DIRECT), + new DefaultLink(PID, d2p1, d3p1, DIRECT), + createEdgeLink(d3p0, false) + ); + private final int hops = links.size() - 1; + private PathIntent intent; + + /** + * Configures objects used in all the test cases. + */ + @Before + public void setUp() { + sut = new PathIntentCompiler(); + coreService = createMock(CoreService.class); + expect(coreService.registerApplication("org.onosproject.net.intent")) + .andReturn(appId); + sut.coreService = coreService; + + Intent.bindIdGenerator(idGenerator); + + intent = PathIntent.builder() + .appId(APP_ID) + .selector(selector) + .treatment(treatment) + .priority(PRIORITY) + .path(new DefaultPath(pid, links, hops)) + .build(); + intentExtensionService = createMock(IntentExtensionService.class); + intentExtensionService.registerCompiler(PathIntent.class, sut); + intentExtensionService.unregisterCompiler(PathIntent.class); + sut.intentManager = intentExtensionService; + + replay(coreService, intentExtensionService); + } + + /** + * Tears down objects used in all the test cases. + */ + @After + public void tearDown() { + Intent.unbindIdGenerator(idGenerator); + } + + /** + * Tests the compilation behavior of the path intent compiler. + */ + @Test + public void testCompile() { + sut.activate(); + + List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet()); + assertThat(compiled, hasSize(1)); + + Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules(); + + FlowRule rule1 = rules.stream() + .filter(x -> x.deviceId().equals(d1p0.deviceId())) + .findFirst() + .get(); + assertThat(rule1.deviceId(), is(d1p0.deviceId())); + assertThat(rule1.selector(), + is(DefaultTrafficSelector.builder(selector).matchInPort(d1p0.port()).build())); + assertThat(rule1.treatment(), + is(DefaultTrafficTreatment.builder().setOutput(d1p1.port()).build())); + assertThat(rule1.priority(), is(intent.priority())); + + FlowRule rule2 = rules.stream() + .filter(x -> x.deviceId().equals(d2p0.deviceId())) + .findFirst() + .get(); + assertThat(rule2.deviceId(), is(d2p0.deviceId())); + assertThat(rule2.selector(), + is(DefaultTrafficSelector.builder(selector).matchInPort(d2p0.port()).build())); + assertThat(rule2.treatment(), + is(DefaultTrafficTreatment.builder().setOutput(d2p1.port()).build())); + assertThat(rule2.priority(), is(intent.priority())); + + FlowRule rule3 = rules.stream() + .filter(x -> x.deviceId().equals(d3p0.deviceId())) + .findFirst() + .get(); + assertThat(rule3.deviceId(), is(d3p1.deviceId())); + assertThat(rule3.selector(), + is(DefaultTrafficSelector.builder(selector).matchInPort(d3p1.port()).build())); + assertThat(rule3.treatment(), + is(DefaultTrafficTreatment.builder(treatment).setOutput(d3p0.port()).build())); + assertThat(rule3.priority(), is(intent.priority())); + + sut.deactivate(); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java new file mode 100644 index 00000000..e57d9dbe --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java @@ -0,0 +1,320 @@ +/* + * Copyright 2014-2015 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.net.intent.impl.compiler; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.onlab.util.Bandwidth; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.AbstractIntentTest; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentTestsMocks; +import org.onosproject.net.intent.PathIntent; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.intent.constraint.BandwidthConstraint; +import org.onosproject.net.intent.constraint.LambdaConstraint; +import org.onosproject.net.intent.impl.PathNotFoundException; +import org.onosproject.net.resource.link.BandwidthResource; +import org.onosproject.net.resource.link.LambdaResource; +import org.onosproject.net.resource.link.LinkResourceService; + +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; +import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.connectPoint; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath; + +/** + * Unit tests for the HostToHost intent compiler. + */ +public class PointToPointIntentCompilerTest extends AbstractIntentTest { + + private static final ApplicationId APPID = new TestApplicationId("foo"); + + private TrafficSelector selector = new IntentTestsMocks.MockSelector(); + private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment(); + + /** + * Creates a PointToPoint intent based on ingress and egress device Ids. + * + * @param ingressIdString string for id of ingress device + * @param egressIdString string for id of egress device + * @return PointToPointIntent for the two devices + */ + private PointToPointIntent makeIntent(String ingressIdString, + String egressIdString) { + return PointToPointIntent.builder() + .appId(APPID) + .selector(selector) + .treatment(treatment) + .ingressPoint(connectPoint(ingressIdString, 1)) + .egressPoint(connectPoint(egressIdString, 1)) + .build(); + } + + /** + * Creates a PointToPoint intent based on ingress and egress deviceIds and constraints. + * + * @param ingressIdString string for id of ingress device + * @param egressIdString string for id of egress device + * @param constraints constraints + * @return PointToPointIntent for the two device with constraints + */ + private PointToPointIntent makeIntent(String ingressIdString, + String egressIdString, List<Constraint> constraints) { + return PointToPointIntent.builder() + .appId(APPID) + .selector(selector) + .treatment(treatment) + .ingressPoint(connectPoint(ingressIdString, 1)) + .egressPoint(connectPoint(egressIdString, 1)) + .constraints(constraints) + .build(); + } + + /** + * Creates a compiler for HostToHost intents. + * + * @param hops string array describing the path hops to use when compiling + * @return HostToHost intent compiler + */ + private PointToPointIntentCompiler makeCompiler(String[] hops) { + PointToPointIntentCompiler compiler = new PointToPointIntentCompiler(); + compiler.pathService = new IntentTestsMocks.MockPathService(hops); + return compiler; + } + + /** + * Creates a point to point intent compiler for a three switch linear + * topology. + * + * @param resourceService service to use for resource allocation requests + * @return point to point compiler + */ + private PointToPointIntentCompiler makeCompiler(String[] hops, LinkResourceService resourceService) { + final PointToPointIntentCompiler compiler = new PointToPointIntentCompiler(); + compiler.resourceService = resourceService; + compiler.pathService = new IntentTestsMocks.MockPathService(hops); + return compiler; + } + + /** + * Tests a pair of devices in an 8 hop path, forward direction. + */ + @Test + public void testForwardPathCompilation() { + + PointToPointIntent intent = makeIntent("d1", "d8"); + + String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"}; + PointToPointIntentCompiler compiler = makeCompiler(hops); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(Matchers.notNullValue())); + assertThat(result, hasSize(1)); + Intent forwardResultIntent = result.get(0); + assertThat(forwardResultIntent instanceof PathIntent, is(true)); + + if (forwardResultIntent instanceof PathIntent) { + PathIntent forwardPathIntent = (PathIntent) forwardResultIntent; + // 7 links for the hops, plus one default lnk on ingress and egress + assertThat(forwardPathIntent.path().links(), hasSize(hops.length + 1)); + assertThat(forwardPathIntent.path().links(), linksHasPath("d1", "d2")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d2", "d3")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d3", "d4")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d4", "d5")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d5", "d6")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d6", "d7")); + assertThat(forwardPathIntent.path().links(), linksHasPath("d7", "d8")); + } + } + + /** + * Tests a pair of devices in an 8 hop path, forward direction. + */ + @Test + public void testReversePathCompilation() { + + PointToPointIntent intent = makeIntent("d8", "d1"); + + String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"}; + PointToPointIntentCompiler compiler = makeCompiler(hops); + + List<Intent> result = compiler.compile(intent, null, null); + assertThat(result, is(Matchers.notNullValue())); + assertThat(result, hasSize(1)); + Intent reverseResultIntent = result.get(0); + assertThat(reverseResultIntent instanceof PathIntent, is(true)); + + if (reverseResultIntent instanceof PathIntent) { + PathIntent reversePathIntent = (PathIntent) reverseResultIntent; + assertThat(reversePathIntent.path().links(), hasSize(hops.length + 1)); + assertThat(reversePathIntent.path().links(), linksHasPath("d2", "d1")); + assertThat(reversePathIntent.path().links(), linksHasPath("d3", "d2")); + assertThat(reversePathIntent.path().links(), linksHasPath("d4", "d3")); + assertThat(reversePathIntent.path().links(), linksHasPath("d5", "d4")); + assertThat(reversePathIntent.path().links(), linksHasPath("d6", "d5")); + assertThat(reversePathIntent.path().links(), linksHasPath("d7", "d6")); + assertThat(reversePathIntent.path().links(), linksHasPath("d8", "d7")); + } + } + + /** + * Tests compilation of the intent which designates two different ports on the same switch. + */ + @Test + public void testSameSwitchDifferentPortsIntentCompilation() { + ConnectPoint src = new ConnectPoint(deviceId("1"), portNumber(1)); + ConnectPoint dst = new ConnectPoint(deviceId("1"), portNumber(2)); + PointToPointIntent intent = PointToPointIntent.builder() + .appId(APP_ID) + .selector(selector) + .treatment(treatment) + .ingressPoint(src) + .egressPoint(dst) + .build(); + + String[] hops = {"1"}; + PointToPointIntentCompiler sut = makeCompiler(hops); + + List<Intent> compiled = sut.compile(intent, null, null); + + assertThat(compiled, hasSize(1)); + assertThat(compiled.get(0), is(instanceOf(PathIntent.class))); + Path path = ((PathIntent) compiled.get(0)).path(); + + assertThat(path.links(), hasSize(2)); + Link firstLink = path.links().get(0); + assertThat(firstLink, is(createEdgeLink(src, true))); + Link secondLink = path.links().get(1); + assertThat(secondLink, is(createEdgeLink(dst, false))); + } + + /** + * Tests that requests with sufficient available bandwidth succeed. + */ + @Test + public void testBandwidthConstrainedIntentSuccess() { + + final LinkResourceService resourceService = + IntentTestsMocks.MockResourceService.makeBandwidthResourceService(1000.0); + final List<Constraint> constraints = + Collections.singletonList(new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(100.0)))); + + final PointToPointIntent intent = makeIntent("s1", "s3", constraints); + + String[] hops = {"s1", "s2", "s3"}; + final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService); + + final List<Intent> compiledIntents = compiler.compile(intent, null, null); + + assertThat(compiledIntents, Matchers.notNullValue()); + assertThat(compiledIntents, hasSize(1)); + } + + /** + * Tests that requests with insufficient available bandwidth fail. + */ + @Test + public void testBandwidthConstrainedIntentFailure() { + + final LinkResourceService resourceService = + IntentTestsMocks.MockResourceService.makeBandwidthResourceService(10.0); + final List<Constraint> constraints = + Collections.singletonList(new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(100.0)))); + + try { + final PointToPointIntent intent = makeIntent("s1", "s3", constraints); + + String[] hops = {"s1", "s2", "s3"}; + final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService); + + compiler.compile(intent, null, null); + + fail("Point to Point compilation with insufficient bandwidth does " + + "not throw exception."); + } catch (PathNotFoundException noPath) { + assertThat(noPath.getMessage(), containsString("No path")); + } + } + + /** + * Tests that requests for available lambdas are successful. + */ + @Test + public void testLambdaConstrainedIntentSuccess() { + + final List<Constraint> constraints = + Collections.singletonList(new LambdaConstraint(LambdaResource.valueOf(1))); + final LinkResourceService resourceService = + IntentTestsMocks.MockResourceService.makeLambdaResourceService(1); + + final PointToPointIntent intent = makeIntent("s1", "s3", constraints); + + String[] hops = {"s1", "s2", "s3"}; + final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService); + + final List<Intent> compiledIntents = + compiler.compile(intent, null, null); + + assertThat(compiledIntents, Matchers.notNullValue()); + assertThat(compiledIntents, hasSize(1)); + } + + /** + * Tests that requests for lambdas when there are no available lambdas + * fail. + */ + @Test + public void testLambdaConstrainedIntentFailure() { + + final List<Constraint> constraints = + Collections.singletonList(new LambdaConstraint(LambdaResource.valueOf(1))); + final LinkResourceService resourceService = + IntentTestsMocks.MockResourceService.makeBandwidthResourceService(10.0); + try { + final PointToPointIntent intent = makeIntent("s1", "s3", constraints); + + String[] hops = {"s1", "s2", "s3"}; + final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService); + + compiler.compile(intent, null, null); + + fail("Point to Point compilation with no available lambda does " + + "not throw exception."); + } catch (PathNotFoundException noPath) { + assertThat(noPath.getMessage(), containsString("No path")); + } + } + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java new file mode 100644 index 00000000..c15ecaec --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2015 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.net.intent.impl.phase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.IdGenerator; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +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.IntentData; +import org.onosproject.net.intent.MockIdGenerator; +import org.onosproject.net.intent.PathIntent; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.intent.impl.IntentCompilationException; +import org.onosproject.net.intent.impl.IntentProcessor; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.Timestamp; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.intent.IntentState.INSTALL_REQ; + +/** + * Unit tests for Compiling phase. + */ +public class CompilingTest { + + private final ApplicationId appId = new TestApplicationId("test"); + private final ProviderId pid = new ProviderId("of", "test"); + private final TrafficSelector selector = DefaultTrafficSelector.emptySelector(); + private final TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); + private final ConnectPoint cp1 = new ConnectPoint(deviceId("1"), portNumber(1)); + private final ConnectPoint cp2 = new ConnectPoint(deviceId("1"), portNumber(2)); + private final ConnectPoint cp3 = new ConnectPoint(deviceId("2"), portNumber(1)); + private final ConnectPoint cp4 = new ConnectPoint(deviceId("2"), portNumber(2)); + + private final List<Link> links = Collections.singletonList(new DefaultLink(pid, cp2, cp4, DIRECT)); + private final Path path = new DefaultPath(pid, links, 10); + + private PointToPointIntent input; + private PathIntent compiled; + + private IdGenerator idGenerator; + private IntentProcessor processor; + private Timestamp version; + + @Before + public void setUp() { + processor = createMock(IntentProcessor.class); + version = createMock(Timestamp.class); + + idGenerator = new MockIdGenerator(); + + Intent.bindIdGenerator(idGenerator); + + // Intent creation should be placed after binding an ID generator + input = PointToPointIntent.builder() + .appId(appId) + .selector(selector) + .treatment(treatment) + .ingressPoint(cp1) + .egressPoint(cp3) + .build(); + compiled = PathIntent.builder() + .appId(appId) + .selector(selector) + .treatment(treatment) + .path(path) + .build(); + } + + + @After + public void tearDown() { + Intent.unbindIdGenerator(idGenerator); + } + + /** + * Tests a next phase when no exception occurs. + */ + @Test + public void testMoveToNextPhaseWithoutError() { + IntentData pending = new IntentData(input, INSTALL_REQ, version); + + expect(processor.compile(input, null)).andReturn(Collections.singletonList(compiled)); + replay(processor); + + Compiling sut = new Compiling(processor, pending, Optional.empty()); + + Optional<IntentProcessPhase> output = sut.execute(); + + verify(processor); + assertThat(output.get(), is(instanceOf(Installing.class))); + } + + /** + * Tests a next phase when IntentCompilationException occurs. + */ + @Test + public void testWhenIntentCompilationExceptionOccurs() { + IntentData pending = new IntentData(input, INSTALL_REQ, version); + + expect(processor.compile(input, null)).andThrow(new IntentCompilationException()); + replay(processor); + + Compiling sut = new Compiling(processor, pending, Optional.empty()); + + Optional<IntentProcessPhase> output = sut.execute(); + + verify(processor); + assertThat(output.get(), is(instanceOf(Failed.class))); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java new file mode 100644 index 00000000..fe9e37cd --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014-2015 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.net.link.impl; + +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.PortNumber.portNumber; +import static org.junit.Assert.assertEquals; + +import java.time.Duration; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigApplyDelegate; +import org.onosproject.net.config.basics.BasicLinkConfig; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.LinkKey; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +public class BasicLinkOperatorTest { + + private static final DeviceId DID1 = deviceId("of:foo"); + private static final DeviceId DID2 = deviceId("of:bar"); + private static final PortNumber P1 = portNumber(1); + + private static final ConnectPoint SRC = new ConnectPoint(DID1, P1); + private static final ConnectPoint DST = new ConnectPoint(DID2, P1); + private static final LinkKey LK = LinkKey.linkKey(SRC, DST); + private static final Duration NTIME = Duration.ofNanos(200); + + private static final SparseAnnotations SA = DefaultAnnotations.builder() + .set(AnnotationKeys.DURABLE, "true").build(); + private static final LinkDescription LD = new DefaultLinkDescription(SRC, DST, Link.Type.DIRECT, SA); + private final ConfigApplyDelegate delegate = new ConfigApplyDelegate() { + @Override + public void onApply(Config config) { + } + }; + private final ObjectMapper mapper = new ObjectMapper(); + + private static final BasicLinkConfig BLC = new BasicLinkConfig(); + + @Before + public void setUp() { + BLC.init(LK, "optest", JsonNodeFactory.instance.objectNode(), mapper, delegate); + BLC.latency(NTIME); + } + + @Test + public void testDescOps() { + LinkDescription desc = BasicLinkOperator.combine(BLC, LD); + assertEquals(NTIME.toString(), desc.annotations().value(AnnotationKeys.LATENCY)); + assertEquals("true", desc.annotations().value(AnnotationKeys.DURABLE)); + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java new file mode 100644 index 00000000..dad5429e --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java @@ -0,0 +1,311 @@ +/* + * Copyright 2014 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.net.link.impl; + +import com.google.common.collect.ImmutableSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.event.Event; +import org.onosproject.net.config.NetworkConfigServiceAdapter; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.PortNumber; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkAdminService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.net.device.impl.DeviceManager; +import org.onosproject.store.trivial.SimpleLinkStore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.Link.Type.INDIRECT; +import static org.onosproject.net.NetTestTools.injectEventDispatcher; +import static org.onosproject.net.link.LinkEvent.Type.*; + +/** + * Test codifying the link service & link provider service contracts. + */ +public class LinkManagerTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final DeviceId DID1 = deviceId("of:foo"); + private static final DeviceId DID2 = deviceId("of:bar"); + private static final DeviceId DID3 = deviceId("of:goo"); + private static final Device DEV1 = new DefaultDevice( + PID, DID1, Device.Type.SWITCH, "", "", "", "", null); + private static final Device DEV2 = new DefaultDevice( + PID, DID2, Device.Type.SWITCH, "", "", "", "", null); + private static final Device DEV3 = new DefaultDevice( + PID, DID2, Device.Type.SWITCH, "", "", "", "", null); + + private static final PortNumber P1 = PortNumber.portNumber(1); + private static final PortNumber P2 = PortNumber.portNumber(2); + private static final PortNumber P3 = PortNumber.portNumber(3); + private static final Map<DeviceId, Device> DEVICEIDMAP = new HashMap<>(); + + private LinkManager mgr; + + protected LinkService service; + protected LinkAdminService admin; + protected LinkProviderRegistry registry; + protected LinkProviderService providerService; + protected TestProvider provider; + protected TestListener listener = new TestListener(); + protected DeviceManager devmgr = new TestDeviceManager(); + + + + @Before + public void setUp() { + mgr = new LinkManager(); + service = mgr; + admin = mgr; + registry = mgr; + mgr.store = new SimpleLinkStore(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + mgr.deviceService = devmgr; + mgr.networkConfigService = new TestNetworkConfigService(); + mgr.activate(); + + DEVICEIDMAP.put(DID1, DEV1); + DEVICEIDMAP.put(DID2, DEV2); + DEVICEIDMAP.put(DID3, DEV3); + + service.addListener(listener); + + provider = new TestProvider(); + providerService = registry.register(provider); + assertTrue("provider should be registered", + registry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + registry.unregister(provider); + assertFalse("provider should not be registered", + registry.getProviders().contains(provider.id())); + service.removeListener(listener); + mgr.deactivate(); + } + + @Test + public void createLink() { + addLink(DID1, P1, DID2, P2, DIRECT); + addLink(DID2, P2, DID1, P1, DIRECT); + assertEquals("incorrect link count", 2, service.getLinkCount()); + + Iterator<Link> it = service.getLinks().iterator(); + it.next(); + it.next(); + assertFalse("incorrect link count", it.hasNext()); + } + + @Test + public void updateLink() { + addLink(DID1, P1, DID2, P2, DIRECT); + addLink(DID2, P2, DID1, P1, INDIRECT); + assertEquals("incorrect link count", 2, service.getLinkCount()); + + providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), DIRECT)); + validateEvents(LINK_UPDATED); + assertEquals("incorrect link count", 2, service.getLinkCount()); + + providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), INDIRECT)); + providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), DIRECT)); + assertEquals("no events expected", 0, listener.events.size()); + } + + @Test + public void removeLink() { + addLink(DID1, P1, DID2, P2, DIRECT); + addLink(DID2, P2, DID1, P1, DIRECT); + assertEquals("incorrect link count", 2, service.getLinkCount()); + + providerService.linkVanished(new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P2), DIRECT)); + validateEvents(LINK_REMOVED); + assertEquals("incorrect link count", 1, service.getLinkCount()); + assertNull("link should not be found", service.getLink(cp(DID1, P1), cp(DID2, P2))); + assertNotNull("link should be found", service.getLink(cp(DID2, P2), cp(DID1, P1))); + + providerService.linkVanished(new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P2), DIRECT)); + assertEquals("no events expected", 0, listener.events.size()); + } + + @Test + public void removeLinksByConnectionPoint() { + Link l1 = addLink(DID1, P1, DID2, P2, DIRECT); + Link l2 = addLink(DID2, P2, DID1, P1, DIRECT); + addLink(DID3, P3, DID2, P1, DIRECT); + addLink(DID2, P1, DID3, P3, DIRECT); + assertEquals("incorrect link count", 4, service.getLinkCount()); + + providerService.linksVanished(cp(DID1, P1)); + assertEquals("incorrect link count", 2, service.getLinkCount()); + assertNull("link should be gone", service.getLink(l1.src(), l1.dst())); + assertNull("link should be gone", service.getLink(l2.src(), l2.dst())); + } + + @Test + public void removeLinksByDevice() { + addLink(DID1, P1, DID2, P2, DIRECT); + addLink(DID2, P2, DID1, P1, DIRECT); + addLink(DID3, P3, DID2, P1, DIRECT); + addLink(DID2, P1, DID3, P3, DIRECT); + Link l5 = addLink(DID3, P1, DID1, P2, DIRECT); + Link l6 = addLink(DID1, P2, DID3, P1, DIRECT); + assertEquals("incorrect link count", 6, service.getLinkCount()); + + providerService.linksVanished(DID2); + assertEquals("incorrect link count", 2, service.getLinkCount()); + assertNotNull("link should not be gone", service.getLink(l5.src(), l5.dst())); + assertNotNull("link should not be gone", service.getLink(l6.src(), l6.dst())); + } + + @Test + public void removeLinksAsAdminByConnectionPoint() { + Link l1 = addLink(DID1, P1, DID2, P2, DIRECT); + Link l2 = addLink(DID2, P2, DID1, P1, DIRECT); + addLink(DID3, P3, DID2, P1, DIRECT); + addLink(DID2, P1, DID3, P3, DIRECT); + assertEquals("incorrect link count", 4, service.getLinkCount()); + + admin.removeLinks(cp(DID1, P1)); + assertEquals("incorrect link count", 2, service.getLinkCount()); + assertNull("link should be gone", service.getLink(l1.src(), l1.dst())); + assertNull("link should be gone", service.getLink(l2.src(), l2.dst())); + } + + @Test + public void removeLinksAsAdminByDevice() { + addLink(DID1, P1, DID2, P2, DIRECT); + addLink(DID2, P2, DID1, P1, DIRECT); + addLink(DID3, P3, DID2, P1, DIRECT); + addLink(DID2, P1, DID3, P3, DIRECT); + Link l5 = addLink(DID3, P1, DID1, P2, DIRECT); + Link l6 = addLink(DID1, P2, DID3, P1, DIRECT); + assertEquals("incorrect link count", 6, service.getLinkCount()); + + admin.removeLinks(DID2); + assertEquals("incorrect link count", 2, service.getLinkCount()); + assertNotNull("link should not be gone", service.getLink(l5.src(), l5.dst())); + assertNotNull("link should not be gone", service.getLink(l6.src(), l6.dst())); + } + + @Test + public void getLinks() { + Link l1 = addLink(DID1, P1, DID2, P2, DIRECT); + Link l2 = addLink(DID2, P2, DID1, P1, DIRECT); + Link l3 = addLink(DID3, P3, DID2, P1, DIRECT); + Link l4 = addLink(DID2, P1, DID3, P3, DIRECT); + assertEquals("incorrect link count", 4, service.getLinkCount()); + + Set<Link> links = service.getLinks(cp(DID1, P1)); + assertEquals("incorrect links", ImmutableSet.of(l1, l2), links); + links = service.getEgressLinks(cp(DID1, P1)); + assertEquals("incorrect links", ImmutableSet.of(l1), links); + links = service.getIngressLinks(cp(DID1, P1)); + assertEquals("incorrect links", ImmutableSet.of(l2), links); + + links = service.getDeviceLinks(DID2); + assertEquals("incorrect links", ImmutableSet.of(l1, l2, l3, l4), links); + links = service.getDeviceLinks(DID3); + assertEquals("incorrect links", ImmutableSet.of(l3, l4), links); + + links = service.getDeviceEgressLinks(DID2); + assertEquals("incorrect links", ImmutableSet.of(l2, l4), links); + links = service.getDeviceIngressLinks(DID2); + assertEquals("incorrect links", ImmutableSet.of(l1, l3), links); + } + + + private Link addLink(DeviceId sd, PortNumber sp, DeviceId dd, PortNumber dp, + Link.Type type) { + providerService.linkDetected(new DefaultLinkDescription(cp(sd, sp), cp(dd, dp), type)); + Link link = listener.events.get(0).subject(); + validateEvents(LINK_ADDED); + return link; + } + + private ConnectPoint cp(DeviceId id, PortNumber portNumber) { + return new ConnectPoint(id, portNumber); + } + + protected void validateEvents(Enum... types) { + int i = 0; + assertEquals("wrong events received", types.length, listener.events.size()); + for (Event event : listener.events) { + assertEquals("incorrect event type", types[i], event.type()); + i++; + } + listener.events.clear(); + } + + + private class TestProvider extends AbstractProvider implements LinkProvider { + private Device deviceReceived; + private MastershipRole roleReceived; + + public TestProvider() { + super(PID); + } + } + + private static class TestListener implements LinkListener { + final List<LinkEvent> events = new ArrayList<>(); + + @Override + public void event(LinkEvent event) { + events.add(event); + } + } + + private static class TestDeviceManager extends DeviceManager { + + @Override + public MastershipRole getRole(DeviceId deviceId) { + return MastershipRole.MASTER; + } + + @Override + public Device getDevice(DeviceId deviceId) { + return DEVICEIDMAP.get(deviceId); + } + + } + private class TestNetworkConfigService extends NetworkConfigServiceAdapter { + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java new file mode 100644 index 00000000..1a160d98 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java @@ -0,0 +1,667 @@ +/* + * Copyright 2014-2015 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.net.proxyarp.impl; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ARP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultHost; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.Link; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.edgeservice.impl.EdgeManager; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.host.HostService; +import org.onosproject.net.host.InterfaceIpAddress; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketServiceAdapter; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.proxyarp.ProxyArpStore; +import org.onosproject.net.proxyarp.ProxyArpStoreDelegate; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for the {@link ProxyArpManager} class. + */ +public class ProxyArpManagerTest { + + private static final int NUM_DEVICES = 6; + private static final int NUM_PORTS_PER_DEVICE = 3; + private static final int NUM_ADDRESS_PORTS = NUM_DEVICES / 2; + private static final int NUM_FLOOD_PORTS = 3; + + 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 ProviderId PID = new ProviderId("of", "foo"); + + private static final VlanId VLAN1 = VlanId.vlanId((short) 1); + private static final VlanId VLAN2 = VlanId.vlanId((short) 2); + private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01"); + private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02"); + private static final HostId HID1 = HostId.hostId(MAC1, VLAN1); + private static final HostId HID2 = HostId.hostId(MAC2, VLAN1); + + private static final DeviceId DID1 = getDeviceId(1); + private static final DeviceId DID2 = getDeviceId(2); + private static final PortNumber P1 = PortNumber.portNumber(1); + private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L); + private static final HostLocation LOC2 = new HostLocation(DID2, P1, 123L); + private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes(); + + //Return values used for various functions of the TestPacketService inner class. + private boolean isEdgePointReturn; + private List<ConnectPoint> getEdgePointsNoArg; + + + private ProxyArpManager proxyArp; + + private TestPacketService packetService; + private DeviceService deviceService; + private LinkService linkService; + private HostService hostService; + private InterfaceService interfaceService; + + @Before + public void setUp() throws Exception { + proxyArp = new ProxyArpManager(); + packetService = new TestPacketService(); + proxyArp.packetService = packetService; + proxyArp.store = new TestProxyArpStoreAdapter(); + + proxyArp.edgeService = new TestEdgePortService(); + + // Create a host service mock here. Must be replayed by tests once the + // expectations have been set up + hostService = createMock(HostService.class); + proxyArp.hostService = hostService; + + interfaceService = createMock(InterfaceService.class); + proxyArp.interfaceService = interfaceService; + + createTopology(); + proxyArp.deviceService = deviceService; + proxyArp.linkService = linkService; + + proxyArp.activate(); + } + + /** + * Creates a fake topology to feed into the ARP module. + * <p> + * The default topology is a unidirectional ring topology. Each switch has + * 3 ports. Ports 2 and 3 have the links to neighbor switches, and port 1 + * is free (edge port). + * The first half of the switches have IP addresses configured on their + * free ports (port 1). The second half of the switches have no IP + * addresses configured. + */ + private void createTopology() { + deviceService = createMock(DeviceService.class); + linkService = createMock(LinkService.class); + + deviceService.addListener(anyObject(DeviceListener.class)); + linkService.addListener(anyObject(LinkListener.class)); + + createDevices(NUM_DEVICES, NUM_PORTS_PER_DEVICE); + createLinks(NUM_DEVICES); + addAddressBindings(); + } + + /** + * Creates the devices for the fake topology. + */ + private void createDevices(int numDevices, int numPorts) { + List<Device> devices = new ArrayList<>(); + + for (int i = 1; i <= numDevices; i++) { + DeviceId devId = getDeviceId(i); + Device device = createMock(Device.class); + expect(device.id()).andReturn(devId).anyTimes(); + replay(device); + + devices.add(device); + + List<Port> ports = new ArrayList<>(); + for (int j = 1; j <= numPorts; j++) { + Port port = createMock(Port.class); + expect(port.number()).andReturn(PortNumber.portNumber(j)).anyTimes(); + replay(port); + ports.add(port); + } + + expect(deviceService.getPorts(devId)).andReturn(ports).anyTimes(); + expect(deviceService.getDevice(devId)).andReturn(device).anyTimes(); + } + + expect(deviceService.getDevices()).andReturn(devices).anyTimes(); + replay(deviceService); + } + + /** + * Creates the links for the fake topology. + * NB: Only unidirectional links are created, as for this purpose all we + * need is to occupy the ports with some link. + */ + private void createLinks(int numDevices) { + List<Link> links = new ArrayList<>(); + + for (int i = 1; i <= numDevices; i++) { + ConnectPoint src = new ConnectPoint( + getDeviceId(i), + PortNumber.portNumber(2)); + ConnectPoint dst = new ConnectPoint( + getDeviceId((i + 1 > numDevices) ? 1 : i + 1), + PortNumber.portNumber(3)); + + Link link = createMock(Link.class); + expect(link.src()).andReturn(src).anyTimes(); + expect(link.dst()).andReturn(dst).anyTimes(); + replay(link); + + links.add(link); + } + + expect(linkService.getLinks()).andReturn(links).anyTimes(); + replay(linkService); + } + + private void addAddressBindings() { + Set<Interface> interfaces = Sets.newHashSet(); + + for (int i = 1; i <= NUM_ADDRESS_PORTS; i++) { + ConnectPoint cp = new ConnectPoint(getDeviceId(i), P1); + Ip4Prefix prefix1 = + Ip4Prefix.valueOf("10.0." + (2 * i - 1) + ".0/24"); + Ip4Address addr1 = + Ip4Address.valueOf("10.0." + (2 * i - 1) + ".1"); + Ip4Prefix prefix2 = Ip4Prefix.valueOf("10.0." + (2 * i) + ".0/24"); + Ip4Address addr2 = Ip4Address.valueOf("10.0." + (2 * i) + ".1"); + InterfaceIpAddress ia1 = new InterfaceIpAddress(addr1, prefix1); + InterfaceIpAddress ia2 = new InterfaceIpAddress(addr2, prefix2); + Interface intf1 = new Interface(cp, Sets.newHashSet(ia1), + MacAddress.valueOf(2 * i - 1), + VlanId.vlanId((short) 1)); + Interface intf2 = new Interface(cp, Sets.newHashSet(ia2), + MacAddress.valueOf(2 * i), + VlanId.NONE); + + interfaces.add(intf1); + interfaces.add(intf2); + + expect(interfaceService.getInterfacesByPort(cp)) + .andReturn(Sets.newHashSet(intf1, intf2)).anyTimes(); + } + + expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes(); + + for (int i = 1; i <= NUM_FLOOD_PORTS; i++) { + ConnectPoint cp = new ConnectPoint(getDeviceId(i + NUM_ADDRESS_PORTS), + P1); + + expect(interfaceService.getInterfacesByPort(cp)) + .andReturn(Collections.emptySet()).anyTimes(); + } + } + + /** + * Tests {@link ProxyArpManager#isKnown(org.onlab.packet.IpAddress)} in the + * case where the IP address is not known. + * Verifies the method returns false. + */ + @Test + public void testNotKnown() { + expect(hostService.getHostsByIp(IP1)).andReturn(Collections.<Host>emptySet()); + replay(hostService); + replay(interfaceService); + + assertFalse(proxyArp.isKnown(IP1)); + } + + /** + * Tests {@link ProxyArpManager#isKnown(org.onlab.packet.IpAddress)} in the + * case where the IP address is known. + * Verifies the method returns true. + */ + @Test + public void testKnown() { + Host host1 = createMock(Host.class); + Host host2 = createMock(Host.class); + + expect(hostService.getHostsByIp(IP1)) + .andReturn(Sets.newHashSet(host1, host2)); + replay(hostService); + replay(interfaceService); + + assertTrue(proxyArp.isKnown(IP1)); + } + + /** + * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the + * destination host is known. + * Verifies the correct ARP reply is sent out the correct port. + */ + @Test + public void testReplyKnown() { + //Set the return value of isEdgePoint from the edgemanager. + isEdgePointReturn = true; + + Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN1, getLocation(4), + Collections.singleton(IP1)); + + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(5), + Collections.singleton(IP2)); + + expect(hostService.getHostsByIp(IP1)) + .andReturn(Collections.singleton(replyer)); + expect(hostService.getHost(HID2)).andReturn(requestor); + + replay(hostService); + replay(interfaceService); + + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1); + + proxyArp.reply(arpRequest, getLocation(5)); + + assertEquals(1, packetService.packets.size()); + Ethernet arpReply = buildArp(ARP.OP_REPLY, MAC1, MAC2, IP1, IP2); + verifyPacketOut(arpReply, getLocation(5), packetService.packets.get(0)); + } + + /** + * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the + * destination host is not known. + * Verifies the ARP request is flooded out the correct edge ports. + */ + @Test + public void testReplyUnknown() { + isEdgePointReturn = true; + + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(5), + Collections.singleton(IP2)); + + expect(hostService.getHostsByIp(IP1)) + .andReturn(Collections.emptySet()); + expect(interfaceService.getInterfacesByIp(IP2)) + .andReturn(Collections.emptySet()); + expect(hostService.getHost(HID2)).andReturn(requestor); + + + replay(hostService); + replay(interfaceService); + + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1); + + //Setup the set of edge ports to be used in the reply method + getEdgePointsNoArg = Lists.newLinkedList(); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1))); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1))); + + proxyArp.reply(arpRequest, getLocation(6)); + + verifyFlood(arpRequest); + } + + /** + * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the + * destination host is known for that IP address, but is not on the same + * VLAN as the source host. + * Verifies the ARP request is flooded out the correct edge ports. + */ + @Test + public void testReplyDifferentVlan() { + + Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN2, getLocation(4), + Collections.singleton(IP1)); + + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(5), + Collections.singleton(IP2)); + + expect(hostService.getHostsByIp(IP1)) + .andReturn(Collections.singleton(replyer)); + expect(interfaceService.getInterfacesByIp(IP2)) + .andReturn(Collections.emptySet()); + expect(hostService.getHost(HID2)).andReturn(requestor); + + replay(hostService); + replay(interfaceService); + + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1); + + //Setup for flood test + getEdgePointsNoArg = Lists.newLinkedList(); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1))); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1))); + proxyArp.reply(arpRequest, getLocation(6)); + + verifyFlood(arpRequest); + } + + @Test + public void testReplyToRequestForUs() { + Ip4Address theirIp = Ip4Address.valueOf("10.0.1.254"); + Ip4Address ourFirstIp = Ip4Address.valueOf("10.0.1.1"); + Ip4Address ourSecondIp = Ip4Address.valueOf("10.0.2.1"); + MacAddress firstMac = MacAddress.valueOf(1L); + MacAddress secondMac = MacAddress.valueOf(2L); + + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1, + Collections.singleton(theirIp)); + + expect(hostService.getHost(HID2)).andReturn(requestor); + replay(hostService); + replay(interfaceService); + + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, theirIp, ourFirstIp); + isEdgePointReturn = true; + proxyArp.reply(arpRequest, LOC1); + + assertEquals(1, packetService.packets.size()); + Ethernet arpReply = buildArp(ARP.OP_REPLY, firstMac, MAC2, ourFirstIp, theirIp); + verifyPacketOut(arpReply, LOC1, packetService.packets.get(0)); + + // Test a request for the second address on that port + packetService.packets.clear(); + arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, theirIp, ourSecondIp); + + proxyArp.reply(arpRequest, LOC1); + + assertEquals(1, packetService.packets.size()); + arpReply = buildArp(ARP.OP_REPLY, secondMac, MAC2, ourSecondIp, theirIp); + verifyPacketOut(arpReply, LOC1, packetService.packets.get(0)); + } + + @Test + public void testReplyExternalPortBadRequest() { + replay(hostService); // no further host service expectations + replay(interfaceService); + + Ip4Address theirIp = Ip4Address.valueOf("10.0.1.254"); + + // Request for a valid external IP address but coming in the wrong port + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC1, null, theirIp, + Ip4Address.valueOf("10.0.3.1")); + proxyArp.reply(arpRequest, LOC1); + assertEquals(0, packetService.packets.size()); + + // Request for a valid internal IP address but coming in an external port + packetService.packets.clear(); + arpRequest = buildArp(ARP.OP_REQUEST, MAC1, null, theirIp, IP1); + proxyArp.reply(arpRequest, LOC1); + assertEquals(0, packetService.packets.size()); + } + + @Test + public void testReplyToRequestFromUs() { + Ip4Address ourIp = Ip4Address.valueOf("10.0.1.1"); + MacAddress ourMac = MacAddress.valueOf(1L); + Ip4Address theirIp = Ip4Address.valueOf("10.0.1.100"); + + expect(hostService.getHostsByIp(theirIp)).andReturn(Collections.emptySet()); + expect(interfaceService.getInterfacesByIp(ourIp)) + .andReturn(Collections.singleton(new Interface(getLocation(1), + Collections.singleton(new InterfaceIpAddress(ourIp, IpPrefix.valueOf("10.0.1.1/24"))), + ourMac, VLAN1))); + expect(hostService.getHost(HostId.hostId(ourMac, VLAN1))).andReturn(null); + replay(hostService); + replay(interfaceService); + + // This is a request from something inside our network (like a BGP + // daemon) to an external host. + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, ourMac, null, ourIp, theirIp); + //Ensure the packet is allowed through (it is not to an internal port) + isEdgePointReturn = true; + + proxyArp.reply(arpRequest, getLocation(5)); + assertEquals(1, packetService.packets.size()); + verifyPacketOut(arpRequest, getLocation(1), packetService.packets.get(0)); + + // The same request from a random external port should fail + packetService.packets.clear(); + proxyArp.reply(arpRequest, getLocation(2)); + assertEquals(0, packetService.packets.size()); + } + + /** + * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the + * destination host is known. + * Verifies the correct ARP request is sent out the correct port. + */ + @Test + public void testForwardToHost() { + Host host1 = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1, + Collections.singleton(IP1)); + Host host2 = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC2, + Collections.singleton(IP2)); + + expect(hostService.getHost(HID1)).andReturn(host1); + expect(hostService.getHost(HID2)).andReturn(host2); + replay(hostService); + replay(interfaceService); + + Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1); + + proxyArp.forward(arpRequest, LOC2); + + assertEquals(1, packetService.packets.size()); + OutboundPacket packet = packetService.packets.get(0); + + verifyPacketOut(arpRequest, LOC1, packet); + } + + /** + * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the + * destination host is not known. + * Verifies the correct ARP request is flooded out the correct edge ports. + */ + @Test + public void testForwardFlood() { + expect(hostService.getHost(HID1)).andReturn(null); + replay(hostService); + replay(interfaceService); + + Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1); + + //populate the list of edges when so that when forward hits flood in the manager it contains the values + //that should continue on + getEdgePointsNoArg = Lists.newLinkedList(); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("3"), PortNumber.portNumber(1))); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1))); + getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1))); + + proxyArp.forward(arpRequest, getLocation(6)); + + verifyFlood(arpRequest); + } + + /** + * Verifies that the given packet was flooded out all available edge ports, + * except for the input port. + * + * @param packet the packet that was expected to be flooded + */ + private void verifyFlood(Ethernet packet) { + // There should be 1 less than NUM_FLOOD_PORTS; the inPort should be excluded. + assertEquals(NUM_FLOOD_PORTS - 1, packetService.packets.size()); + + Collections.sort(packetService.packets, + (o1, o2) -> o1.sendThrough().uri().compareTo(o2.sendThrough().uri())); + + + for (int i = 0; i < NUM_FLOOD_PORTS - 1; i++) { + ConnectPoint cp = new ConnectPoint(getDeviceId(NUM_ADDRESS_PORTS + i + 1), + PortNumber.portNumber(1)); + + OutboundPacket outboundPacket = packetService.packets.get(i); + verifyPacketOut(packet, cp, outboundPacket); + } + } + + /** + * Verifies the given packet was sent out the given port. + * + * @param expected the packet that was expected to be sent + * @param outPort the port the packet was expected to be sent out + * @param actual the actual OutboundPacket to verify + */ + private void verifyPacketOut(Ethernet expected, ConnectPoint outPort, + OutboundPacket actual) { + assertArrayEquals(expected.serialize(), actual.data().array()); + assertEquals(1, actual.treatment().immediate().size()); + assertEquals(outPort.deviceId(), actual.sendThrough()); + Instruction instruction = actual.treatment().immediate().get(0); + assertTrue(instruction instanceof OutputInstruction); + assertEquals(outPort.port(), ((OutputInstruction) instruction).port()); + } + + /** + * Returns the device ID of the ith device. + * + * @param i device to get the ID of + * @return the device ID + */ + private static DeviceId getDeviceId(int i) { + return DeviceId.deviceId("" + i); + } + + private static HostLocation getLocation(int i) { + return new HostLocation(new ConnectPoint(getDeviceId(i), P1), 123L); + } + + /** + * Builds an ARP packet with the given parameters. + * + * @param opcode opcode of the ARP packet + * @param srcMac source MAC address + * @param dstMac destination MAC address, or null if this is a request + * @param srcIp source IP address + * @param dstIp destination IP address + * @return the ARP packet + */ + private Ethernet buildArp(short opcode, MacAddress srcMac, MacAddress dstMac, + Ip4Address srcIp, Ip4Address dstIp) { + Ethernet eth = new Ethernet(); + + if (dstMac == null) { + eth.setDestinationMACAddress(MacAddress.BROADCAST); + } else { + eth.setDestinationMACAddress(dstMac); + } + + eth.setSourceMACAddress(srcMac); + eth.setEtherType(Ethernet.TYPE_ARP); + eth.setVlanID(VLAN1.toShort()); + + ARP arp = new ARP(); + arp.setOpCode(opcode); + arp.setProtocolType(ARP.PROTO_TYPE_IP); + arp.setHardwareType(ARP.HW_TYPE_ETHERNET); + + arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH); + arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH); + arp.setSenderHardwareAddress(srcMac.toBytes()); + + if (dstMac == null) { + arp.setTargetHardwareAddress(ZERO_MAC_ADDRESS); + } else { + arp.setTargetHardwareAddress(dstMac.toBytes()); + } + + arp.setSenderProtocolAddress(srcIp.toOctets()); + arp.setTargetProtocolAddress(dstIp.toOctets()); + + eth.setPayload(arp); + return eth; + } + + /** + * Test PacketService implementation that simply stores OutboundPackets + * passed to {@link #emit(OutboundPacket)} for later verification. + */ + class TestPacketService extends PacketServiceAdapter { + + List<OutboundPacket> packets = new ArrayList<>(); + + @Override + public void emit(OutboundPacket packet) { + packets.add(packet); + } + + } + + class TestEdgePortService extends EdgeManager { + + @Override + public boolean isEdgePoint(ConnectPoint connectPoint) { + return isEdgePointReturn; + } + + @Override + public Iterable<ConnectPoint> getEdgePoints() { + return getEdgePointsNoArg; + } + } + + private class TestProxyArpStoreAdapter implements ProxyArpStore { + @Override + public void forward(ConnectPoint outPort, Host subject, ByteBuffer packet) { + TrafficTreatment tt = DefaultTrafficTreatment.builder().setOutput(outPort.port()).build(); + packetService.emit(new DefaultOutboundPacket(outPort.deviceId(), tt, packet)); + } + + @Override + public void setDelegate(ProxyArpStoreDelegate delegate) { + } + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java new file mode 100644 index 00000000..022df23d --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java @@ -0,0 +1,194 @@ +/* + * Copyright 2014-2015 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.net.topology.impl; + +import com.google.common.collect.ImmutableSet; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.event.Event; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.net.Device; +import org.onosproject.net.Link; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.impl.DeviceManager; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.impl.LinkManager; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.TopologyProvider; +import org.onosproject.net.topology.TopologyProviderRegistry; +import org.onosproject.net.topology.TopologyProviderService; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import static org.onosproject.net.NetTestTools.device; +import static org.onosproject.net.NetTestTools.link; +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED; +import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; + +/** + * Test of the default topology provider implementation. + */ +public class DefaultTopologyProviderTest { + + private DefaultTopologyProvider provider = new DefaultTopologyProvider(); + private TestTopoRegistry topologyService = new TestTopoRegistry(); + private TestDeviceService deviceService = new TestDeviceService(); + private TestLinkService linkService = new TestLinkService(); + private TestTopoProviderService providerService; + + // phase corresponds to number of topologyChanged called + private Phaser topologyChangedCounts = new Phaser(1); + + @Before + public void setUp() { + provider.deviceService = deviceService; + provider.linkService = linkService; + provider.providerRegistry = topologyService; + provider.cfgService = new ComponentConfigAdapter(); + provider.activate(null); + } + + @After + public void tearDown() { + provider.deactivate(null); + provider.providerRegistry = null; + provider.deviceService = null; + provider.linkService = null; + provider.cfgService = null; + } + + private void validateSubmission() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + assertNotNull("topo change should be submitted", providerService.graphDesc); + assertEquals("incorrect vertex count", 6, providerService.graphDesc.vertexes().size()); + assertEquals("incorrect edge count", 10, providerService.graphDesc.edges().size()); + } + + @Test + public void basics() throws InterruptedException, TimeoutException { + assertEquals(1, topologyChangedCounts.awaitAdvanceInterruptibly(0, 1, TimeUnit.SECONDS)); + validateSubmission(); + } + + @Test + public void eventDriven() throws InterruptedException, TimeoutException { + assertEquals(1, topologyChangedCounts.awaitAdvanceInterruptibly(0, 1, TimeUnit.SECONDS)); + validateSubmission(); + + deviceService.postEvent(new DeviceEvent(DEVICE_ADDED, device("z"), null)); + linkService.postEvent(new LinkEvent(LINK_ADDED, link("z", 1, "a", 4))); + assertThat(topologyChangedCounts.awaitAdvanceInterruptibly(1, 1, TimeUnit.SECONDS), + is(greaterThanOrEqualTo(2))); + // Note: posting event, to trigger topologyChanged call, + // but dummy topology will not change. + validateSubmission(); + } + + + private class TestTopoRegistry implements TopologyProviderRegistry { + + @Override + public TopologyProviderService register(TopologyProvider provider) { + providerService = new TestTopoProviderService(provider); + return providerService; + } + + @Override + public void unregister(TopologyProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + } + + private class TestTopoProviderService + extends AbstractProviderService<TopologyProvider> + implements TopologyProviderService { + GraphDescription graphDesc; + + protected TestTopoProviderService(TopologyProvider provider) { + super(provider); + } + + @Override + public void topologyChanged(GraphDescription graphDescription, List<Event> reasons) { + graphDesc = graphDescription; + topologyChangedCounts.arrive(); + } + } + + private class TestDeviceService extends DeviceManager { + TestDeviceService() { + eventDispatcher = new TestEventDispatcher(); + eventDispatcher.addSink(DeviceEvent.class, listenerRegistry); + } + + @Override + public Iterable<Device> getDevices() { + return ImmutableSet.of(device("a"), device("b"), + device("c"), device("d"), + device("e"), device("f")); + } + + @Override + public Iterable<Device> getAvailableDevices() { + return getDevices(); + } + + void postEvent(DeviceEvent event) { + eventDispatcher.post(event); + } + } + + private class TestLinkService extends LinkManager { + TestLinkService() { + eventDispatcher = new TestEventDispatcher(); + eventDispatcher.addSink(LinkEvent.class, listenerRegistry); + } + + @Override + public Iterable<Link> getLinks() { + return ImmutableSet.of(link("a", 1, "b", 1), link("b", 1, "a", 1), + link("b", 2, "c", 1), link("c", 1, "b", 2), + link("c", 2, "d", 1), link("d", 1, "c", 2), + link("d", 2, "a", 2), link("a", 2, "d", 2), + link("e", 1, "f", 1), link("f", 1, "e", 1)); + } + + @Override + public Iterable<Link> getActiveLinks() { + return getLinks(); + } + + void postEvent(LinkEvent event) { + eventDispatcher.post(event); + } + } +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java new file mode 100644 index 00000000..2a2d0b54 --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2014 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.net.topology.impl; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.net.DeviceId; +import org.onosproject.net.ElementId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.Path; +import org.onosproject.net.host.HostService; +import org.onosproject.net.host.HostServiceAdapter; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.PathService; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyService; +import org.onosproject.net.topology.TopologyServiceAdapter; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.onosproject.net.NetTestTools.*; + +/** + * Test of the path selection subsystem. + */ +public class PathManagerTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + + private PathManager mgr; + private PathService service; + + private FakeTopoMgr fakeTopoMgr = new FakeTopoMgr(); + private FakeHostMgr fakeHostMgr = new FakeHostMgr(); + + @Before + public void setUp() { + mgr = new PathManager(); + service = mgr; + mgr.topologyService = fakeTopoMgr; + mgr.hostService = fakeHostMgr; + mgr.activate(); + } + + @After + public void tearDown() { + mgr.deactivate(); + } + + @Test + public void infraToInfra() { + DeviceId src = did("src"); + DeviceId dst = did("dst"); + fakeTopoMgr.paths.add(createPath("src", "middle", "dst")); + Set<Path> paths = service.getPaths(src, dst); + validatePaths(paths, 1, 2, src, dst); + } + + @Test + public void infraToEdge() { + DeviceId src = did("src"); + HostId dst = hid("12:34:56:78:90:ab/1"); + fakeTopoMgr.paths.add(createPath("src", "middle", "edge")); + fakeHostMgr.hosts.put(dst, host("12:34:56:78:90:ab/1", "edge")); + Set<Path> paths = service.getPaths(src, dst); + validatePaths(paths, 1, 3, src, dst); + } + + @Test + public void edgeToInfra() { + HostId src = hid("12:34:56:78:90:ab/1"); + DeviceId dst = did("dst"); + fakeTopoMgr.paths.add(createPath("edge", "middle", "dst")); + fakeHostMgr.hosts.put(src, host("12:34:56:78:90:ab/1", "edge")); + Set<Path> paths = service.getPaths(src, dst); + validatePaths(paths, 1, 3, src, dst); + } + + @Test + public void edgeToEdge() { + HostId src = hid("12:34:56:78:90:ab/1"); + HostId dst = hid("12:34:56:78:90:ef/1"); + fakeTopoMgr.paths.add(createPath("srcEdge", "middle", "dstEdge")); + fakeHostMgr.hosts.put(src, host("12:34:56:78:90:ab/1", "srcEdge")); + fakeHostMgr.hosts.put(dst, host("12:34:56:78:90:ef/1", "dstEdge")); + Set<Path> paths = service.getPaths(src, dst); + validatePaths(paths, 1, 4, src, dst); + } + + @Test + public void edgeToEdgeDirect() { + HostId src = hid("12:34:56:78:90:ab/1"); + HostId dst = hid("12:34:56:78:90:ef/1"); + fakeHostMgr.hosts.put(src, host("12:34:56:78:90:ab/1", "edge")); + fakeHostMgr.hosts.put(dst, host("12:34:56:78:90:ef/1", "edge")); + Set<Path> paths = service.getPaths(src, dst); + validatePaths(paths, 1, 2, src, dst); + } + + @Test + public void noEdge() { + Set<Path> paths = service.getPaths(hid("12:34:56:78:90:ab/1"), + hid("12:34:56:78:90:ef/1")); + assertTrue("there should be no paths", paths.isEmpty()); + } + + // Makes sure the set of paths meets basic expectations. + private void validatePaths(Set<Path> paths, int count, int length, + ElementId src, ElementId dst) { + assertEquals("incorrect path count", count, paths.size()); + for (Path path : paths) { + assertEquals("incorrect length", length, path.links().size()); + assertEquals("incorrect source", src, path.src().elementId()); + assertEquals("incorrect destination", dst, path.dst().elementId()); + } + } + + // Fake entity to give out paths. + private class FakeTopoMgr extends TopologyServiceAdapter implements TopologyService { + Set<Path> paths = new HashSet<>(); + + @Override + public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) { + return paths; + } + + @Override + public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst, LinkWeight weight) { + return paths; + } + } + + // Fake entity to give out hosts. + private class FakeHostMgr extends HostServiceAdapter implements HostService { + private Map<HostId, Host> hosts = new HashMap<>(); + + @Override + public Host getHost(HostId hostId) { + return hosts.get(hostId); + } + } + +} diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java new file mode 100644 index 00000000..f3cd28df --- /dev/null +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java @@ -0,0 +1,215 @@ +/* + * Copyright 2014-2015 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.net.topology.impl; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.event.Event; +import org.onosproject.common.event.impl.TestEventDispatcher; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.DefaultGraphDescription; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyCluster; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyGraph; +import org.onosproject.net.topology.TopologyListener; +import org.onosproject.net.topology.TopologyProvider; +import org.onosproject.net.topology.TopologyProviderRegistry; +import org.onosproject.net.topology.TopologyProviderService; +import org.onosproject.net.topology.TopologyService; +import org.onosproject.store.trivial.SimpleTopologyStore; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.*; +import static org.onosproject.net.NetTestTools.*; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.topology.ClusterId.clusterId; +import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED; + +/** + * Test of the topology subsystem. + */ +public class TopologyManagerTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + + private TopologyManager mgr; + + protected TopologyService service; + protected TopologyProviderRegistry registry; + protected TopologyProviderService providerService; + protected TestProvider provider; + protected TestListener listener = new TestListener(); + + @Before + public void setUp() { + mgr = new TopologyManager(); + service = mgr; + registry = mgr; + + mgr.store = new SimpleTopologyStore(); + injectEventDispatcher(mgr, new TestEventDispatcher()); + mgr.activate(); + + service.addListener(listener); + + provider = new TestProvider(); + providerService = registry.register(provider); + + assertTrue("provider should be registered", + registry.getProviders().contains(provider.id())); + } + + @After + public void tearDown() { + mgr.deactivate(); + service.removeListener(listener); + } + + @Test + public void basics() { + Topology topology = service.currentTopology(); + assertNull("no topo expected", topology); + submitTopologyGraph(); + validateEvents(TOPOLOGY_CHANGED); + topology = service.currentTopology(); + assertTrue("should be latest", service.isLatest(topology)); + + submitTopologyGraph(); + validateEvents(TOPOLOGY_CHANGED); + assertFalse("should be latest", service.isLatest(topology)); + } + + private void submitTopologyGraph() { + Set<Device> devices = of(device("a"), device("b"), + device("c"), device("d"), + device("e"), device("f")); + Set<Link> links = of(link("a", 1, "b", 1), link("b", 1, "a", 1), + link("b", 2, "c", 1), link("c", 1, "b", 2), + link("c", 2, "d", 1), link("d", 1, "c", 2), + link("d", 2, "a", 2), link("a", 2, "d", 2), + link("e", 1, "f", 1), link("f", 1, "e", 1)); + GraphDescription data = new DefaultGraphDescription(4321L, devices, links); + providerService.topologyChanged(data, null); + } + + @Test + public void clusters() { + submitTopologyGraph(); + Topology topology = service.currentTopology(); + assertNotNull("topo expected", topology); + assertEquals("wrong cluster count", 2, topology.clusterCount()); + assertEquals("wrong device count", 6, topology.deviceCount()); + assertEquals("wrong link count", 10, topology.linkCount()); + + assertEquals("wrong cluster count", 2, service.getClusters(topology).size()); + + TopologyCluster cluster = service.getCluster(topology, clusterId(0)); + assertEquals("wrong device count", 4, cluster.deviceCount()); + assertEquals("wrong device count", 4, service.getClusterDevices(topology, cluster).size()); + assertEquals("wrong link count", 8, cluster.linkCount()); + assertEquals("wrong link count", 8, service.getClusterLinks(topology, cluster).size()); + } + + @Test + public void structure() { + submitTopologyGraph(); + Topology topology = service.currentTopology(); + + assertTrue("should be infrastructure point", + service.isInfrastructure(topology, new ConnectPoint(did("a"), portNumber(1)))); + assertFalse("should not be infrastructure point", + service.isInfrastructure(topology, new ConnectPoint(did("a"), portNumber(3)))); + + assertTrue("should be broadcast point", + service.isBroadcastPoint(topology, new ConnectPoint(did("a"), portNumber(3)))); + } + + @Test + public void graph() { + submitTopologyGraph(); + Topology topology = service.currentTopology(); + TopologyGraph graph = service.getGraph(topology); + assertEquals("wrong vertex count", 6, graph.getVertexes().size()); + assertEquals("wrong edge count", 10, graph.getEdges().size()); + } + + @Test + public void precomputedPath() { + submitTopologyGraph(); + Topology topology = service.currentTopology(); + Set<Path> paths = service.getPaths(topology, did("a"), did("c")); + assertEquals("wrong path count", 2, paths.size()); + Path path = paths.iterator().next(); + assertEquals("wrong path length", 2, path.links().size()); + assertEquals("wrong path cost", 2, path.cost(), 0.01); + } + + @Test + public void onDemandPath() { + submitTopologyGraph(); + Topology topology = service.currentTopology(); + LinkWeight weight = edge -> 3.3; + + Set<Path> paths = service.getPaths(topology, did("a"), did("c"), weight); + assertEquals("wrong path count", 2, paths.size()); + Path path = paths.iterator().next(); + assertEquals("wrong path length", 2, path.links().size()); + assertEquals("wrong path cost", 6.6, path.cost(), 0.01); + } + + protected void validateEvents(Enum... types) { + int i = 0; + assertEquals("wrong events received", types.length, listener.events.size()); + for (Event event : listener.events) { + assertEquals("incorrect event type", types[i], event.type()); + i++; + } + listener.events.clear(); + } + + private class TestProvider extends AbstractProvider implements TopologyProvider { + public TestProvider() { + super(PID); + } + + @Override + public void triggerRecompute() { + } + } + + private static class TestListener implements TopologyListener { + final List<TopologyEvent> events = new ArrayList<>(); + + @Override + public void event(TopologyEvent event) { + events.add(event); + } + } + +} |