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/main/java | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/core/net/src/main/java')
107 files changed, 15355 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; |