From 1d7c9cbe6d99005cfa7dd67d71b6731e430de60f Mon Sep 17 00:00:00 2001 From: HIGUCHI Yuta Date: Wed, 20 Jan 2016 18:22:36 -0800 Subject: [PATCH] ONOS-3732 Bandwidth resource registration using netcfg. - Uses netcfg defined value as available resource if defined, else uses port speed as available Bandwidth resource Change-Id: I2dde9a9194025194ed8785b4608f064debab182b --- .../onosproject/cli/net/ResourcesCommand.java | 8 +- .../org/onosproject/net/config/Config.java | 44 ++++- .../net/newresource/BandwidthCapacity.java | 85 ++++++++++ .../onosproject/net/newresource/Resource.java | 1 + .../impl/ResourceDeviceListener.java | 44 ++++- .../impl/ResourceNetworkConfigListener.java | 160 ++++++++++++++++++ .../newresource/impl/ResourceRegistrar.java | 46 ++++- .../config/samples/network-cfg-bandwidth.json | 14 ++ 8 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 core/api/src/main/java/org/onosproject/net/newresource/BandwidthCapacity.java create mode 100644 core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceNetworkConfigListener.java create mode 100644 tools/package/config/samples/network-cfg-bandwidth.json diff --git a/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java b/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java index 1f79113271..416b1aebaf 100644 --- a/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java +++ b/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java @@ -113,9 +113,15 @@ public class ResourcesCommand extends AbstractShellCommand { return; } + if (resource instanceof ContinuousResource) { + String s = ((String) resource.last()); + String simpleName = s.substring(s.lastIndexOf('.') + 1); print("%s%s: %f", Strings.repeat(" ", level), - resource.last(), + simpleName, + // Note: last() does not return, what we've registered + // following does not work + //((Class) resource.last()).getSimpleName(), ((ContinuousResource) resource).value()); // Continuous resource is terminal node, stop here return; diff --git a/core/api/src/main/java/org/onosproject/net/config/Config.java b/core/api/src/main/java/org/onosproject/net/config/Config.java index aac132079f..1e2ee12b17 100644 --- a/core/api/src/main/java/org/onosproject/net/config/Config.java +++ b/core/api/src/main/java/org/onosproject/net/config/Config.java @@ -190,6 +190,17 @@ public abstract class Config { return object.path(name).asBoolean(defaultValue); } + /** + * Clears the specified property. + * + * @param name property name + * @return self + */ + protected Config clear(String name) { + object.remove(name); + return this; + } + /** * Sets the specified property as a boolean or clears it if null value given. * @@ -437,7 +448,38 @@ public abstract class Config { */ protected boolean isNumber(String field, FieldPresence presence, long... minMax) { JsonNode node = object.path(field); - return isValid(node, presence, (node.isLong() || node.isInt()) && + return isValid(node, presence, node.isNumber() && + (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) && + (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2)); + } + /** + * Indicates whether the specified field holds a valid number. + * + * @param field JSON field name + * @param presence specifies if field is optional or mandatory + * @param minMax optional min/max values + * @return true if valid; false otherwise + * @throws IllegalArgumentException if field is present, but not valid + */ + protected boolean isNumber(String field, FieldPresence presence, double... minMax) { + JsonNode node = object.path(field); + return isValid(node, presence, node.isNumber() && + (minMax.length > 0 && minMax[0] <= node.asDouble() || minMax.length < 1) && + (minMax.length > 1 && minMax[1] > node.asDouble() || minMax.length < 2)); + } + + /** + * Indicates whether the specified field holds a valid integer. + * + * @param field JSON field name + * @param presence specifies if field is optional or mandatory + * @param minMax optional min/max values + * @return true if valid; false otherwise + * @throws IllegalArgumentException if field is present, but not valid + */ + protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) { + JsonNode node = object.path(field); + return isValid(node, presence, node.isIntegralNumber() && (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) && (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2)); } diff --git a/core/api/src/main/java/org/onosproject/net/newresource/BandwidthCapacity.java b/core/api/src/main/java/org/onosproject/net/newresource/BandwidthCapacity.java new file mode 100644 index 0000000000..f7cb3c42a7 --- /dev/null +++ b/core/api/src/main/java/org/onosproject/net/newresource/BandwidthCapacity.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.onlab.util.Bandwidth; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.config.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.annotations.Beta; + +/** + * Configuration to specify maximum available bandwidth resource (Capacity) on a port. + */ +@Beta +public class BandwidthCapacity extends Config { + + /** + * netcfg ConfigKey for {@link BandwidthCapacity}. + */ + public static final String CONFIG_KEY = "bandwidthCapacity"; + + // JSON key + private static final String CAPACITY = "capacityMbps"; + + private static final Logger log = LoggerFactory.getLogger(BandwidthCapacity.class); + + @Override + public boolean isValid() { + // Open for extension (adding fields) in the future, + // must have CAPACITY field. + return isNumber(CAPACITY, FieldPresence.MANDATORY); + } + + /** + * Sets the Available Bandwidth resource (Capacity). + * + * @param bandwidth value to set. + * @return self + */ + public BandwidthCapacity capacity(Bandwidth bandwidth) { + checkNotNull(bandwidth); + + // TODO current Bandwidth API end up value converted to double + setOrClear(CAPACITY, bandwidth.bps()); + return this; + } + + /** + * Available Bandwidth resource (Capacity). + * + * @return {@link Bandwidth} + */ + public Bandwidth capacity() { + JsonNode v = object.path(CAPACITY); + + if (v.isIntegralNumber()) { + + return Bandwidth.mbps(v.asLong()); + } else if (v.isFloatingPointNumber()) { + + return Bandwidth.mbps(v.asDouble()); + } else { + log.warn("Unexpected JsonNode for {}: {}", CAPACITY, v); + return Bandwidth.mbps(v.asDouble()); + } + } +} diff --git a/core/api/src/main/java/org/onosproject/net/newresource/Resource.java b/core/api/src/main/java/org/onosproject/net/newresource/Resource.java index da80f77a98..e3c5c47d09 100644 --- a/core/api/src/main/java/org/onosproject/net/newresource/Resource.java +++ b/core/api/src/main/java/org/onosproject/net/newresource/Resource.java @@ -16,6 +16,7 @@ package org.onosproject.net.newresource; import com.google.common.annotations.Beta; + import org.onosproject.net.DeviceId; import org.onosproject.net.PortNumber; diff --git a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java index b72e693bba..e3e42ac4cf 100644 --- a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java +++ b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java @@ -18,7 +18,9 @@ package org.onosproject.net.newresource.impl; import com.google.common.collect.ImmutableSet; import org.onlab.packet.MplsLabel; import org.onlab.packet.VlanId; +import org.onlab.util.Bandwidth; import org.onlab.util.ItemNotFoundException; +import org.onosproject.net.ConnectPoint; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; import org.onosproject.net.OchSignal; @@ -29,17 +31,20 @@ import org.onosproject.net.behaviour.LambdaQuery; import org.onosproject.net.behaviour.MplsQuery; import org.onosproject.net.behaviour.TributarySlotQuery; import org.onosproject.net.behaviour.VlanQuery; +import org.onosproject.net.config.NetworkConfigService; import org.onosproject.net.device.DeviceEvent; import org.onosproject.net.device.DeviceListener; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.DriverHandler; import org.onosproject.net.driver.DriverService; import org.onosproject.net.newresource.ResourceAdminService; +import org.onosproject.net.newresource.BandwidthCapacity; import org.onosproject.net.newresource.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; @@ -56,21 +61,25 @@ final class ResourceDeviceListener implements DeviceListener { private final ResourceAdminService adminService; private final DeviceService deviceService; private final DriverService driverService; + private final NetworkConfigService netcfgService; private final ExecutorService executor; + /** * Creates an instance with the specified ResourceAdminService and ExecutorService. * * @param adminService instance invoked to register resources * @param deviceService {@link DeviceService} to be used * @param driverService {@link DriverService} to be used + * @param netcfgService {@link NetworkConfigService} to be used. * @param executor executor used for processing resource registration */ ResourceDeviceListener(ResourceAdminService adminService, DeviceService deviceService, DriverService driverService, - ExecutorService executor) { + NetworkConfigService netcfgService, ExecutorService executor) { this.adminService = checkNotNull(adminService); this.deviceService = checkNotNull(deviceService); this.driverService = checkNotNull(driverService); + this.netcfgService = checkNotNull(netcfgService); this.executor = checkNotNull(executor); } @@ -121,6 +130,15 @@ final class ResourceDeviceListener implements DeviceListener { executor.submit(() -> { adminService.registerResources(portPath); + queryBandwidth(device.id(), port.number()) + .map(bw -> portPath.child(Bandwidth.class, bw.bps())) + .map(adminService::registerResources) + .ifPresent(success -> { + if (!success) { + log.error("Failed to register Bandwidth for {}", portPath.id()); + } + }); + // for VLAN IDs Set vlans = queryVlanIds(device.id(), port.number()); if (!vlans.isEmpty()) { @@ -160,6 +178,30 @@ final class ResourceDeviceListener implements DeviceListener { executor.submit(() -> adminService.unregisterResources(resource)); } + /** + * Query bandwidth capacity on a port. + * + * @param did {@link DeviceId} + * @param number {@link PortNumber} + * @return bandwidth capacity + */ + private Optional queryBandwidth(DeviceId did, PortNumber number) { + // Check and use netcfg first. + ConnectPoint cp = new ConnectPoint(did, number); + BandwidthCapacity config = netcfgService.getConfig(cp, BandwidthCapacity.class); + if (config != null) { + log.trace("Registering configured bandwidth {} for {}/{}", config.capacity(), did, number); + return Optional.of(config.capacity()); + } + + // populate bandwidth value, assuming portSpeed == bandwidth + Port port = deviceService.getPort(did, number); + if (port != null) { + return Optional.of(Bandwidth.mbps(port.portSpeed())); + } + return Optional.empty(); + } + private Set queryLambdas(DeviceId did, PortNumber port) { try { DriverHandler handler = driverService.createHandler(did); diff --git a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceNetworkConfigListener.java b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceNetworkConfigListener.java new file mode 100644 index 0000000000..88d8bc6cd6 --- /dev/null +++ b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceNetworkConfigListener.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.newresource.Resource.continuous; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import org.onlab.util.Bandwidth; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.newresource.BandwidthCapacity; +import org.onosproject.net.newresource.ResourceAdminService; +import org.slf4j.Logger; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableSet; + +// TODO Consider merging this with ResourceDeviceListener. +/** + * Handler for NetworkConfiguration changes. + */ +@Beta +final class ResourceNetworkConfigListener implements NetworkConfigListener { + + /** + * Config classes relevant to this listener. + */ + private static final Set> CONFIG_CLASSES = ImmutableSet.of(BandwidthCapacity.class); + + private final Logger log = getLogger(getClass()); + + private final ResourceAdminService adminService; + private final NetworkConfigService cfgService; + private final ExecutorService executor; + + /** + * Creates an instance of listener. + * + * @param adminService {@link ResourceAdminService} + * @param cfgService {@link NetworkConfigService} + * @param executor Executor to use. + */ + ResourceNetworkConfigListener(ResourceAdminService adminService, NetworkConfigService cfgService, + ExecutorService executor) { + this.adminService = checkNotNull(adminService); + this.cfgService = checkNotNull(cfgService); + this.executor = checkNotNull(executor); + } + + @Override + public boolean isRelevant(NetworkConfigEvent event) { + return CONFIG_CLASSES.contains(event.configClass()); + } + + @Override + public void event(NetworkConfigEvent event) { + if (event.configClass() == BandwidthCapacity.class) { + executor.submit(() -> { + try { + handleBandwidthCapacity(event); + } catch (Exception e) { + log.error("Exception handling BandwidthCapacity", e); + } + }); + } + } + + private void handleBandwidthCapacity(NetworkConfigEvent event) { + checkArgument(event.configClass() == BandwidthCapacity.class); + + ConnectPoint cp = (ConnectPoint) event.subject(); + BandwidthCapacity bwCapacity = cfgService.getConfig(cp, BandwidthCapacity.class); + + switch (event.type()) { + case CONFIG_ADDED: + if (!adminService.registerResources(continuous(bwCapacity.capacity().bps(), + cp.deviceId(), + cp.port(), Bandwidth.class))) { + log.info("Failed to register Bandwidth for {}, attempting update", cp); + + // Bandwidth based on port speed, was probably already registered. + // need to update to the valued based on configuration + + if (!updateRegistration(cp, bwCapacity)) { + log.warn("Failed to update Bandwidth for {}", cp); + } + } + break; + + case CONFIG_UPDATED: + if (!updateRegistration(cp, bwCapacity)) { + log.warn("Failed to update Bandwidth for {}", cp); + } + break; + + case CONFIG_REMOVED: + // FIXME Following should be an update to the value based on port speed + if (!adminService.unregisterResources(continuous(0, + cp.deviceId(), + cp.port(), + Bandwidth.class))) { + log.warn("Failed to unregister Bandwidth for {}", cp); + } + break; + + case CONFIG_REGISTERED: + case CONFIG_UNREGISTERED: + // no-op + break; + + default: + break; + } + } + + private boolean updateRegistration(ConnectPoint cp, BandwidthCapacity bwCapacity) { + // FIXME workaround until replace/update semantics become available + // this potentially blows up existing registration + // or end up as no-op + // + // Current code end up in situation like below: + // PortNumber: 2 + // MplsLabel: [[16‥240)] + // VlanId: [[0‥4095)] + // Bandwidth: 2000000.000000 + // Bandwidth: 20000000.000000 + // + // but both unregisterResources(..) and registerResources(..) + // returns true (success) + + if (!adminService.unregisterResources(continuous(0, cp.deviceId(), cp.port(), Bandwidth.class))) { + log.warn("unregisterResources for {} failed", cp); + } + return adminService.registerResources(continuous(bwCapacity.capacity().bps(), + cp.deviceId(), + cp.port(), + Bandwidth.class)); + } + +} diff --git a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java index c3863ca51b..27d5333e1d 100644 --- a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java +++ b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java @@ -16,20 +16,31 @@ package org.onosproject.net.newresource.impl; import com.google.common.annotations.Beta; +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.onosproject.net.ConnectPoint; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigRegistry; import org.onosproject.net.device.DeviceListener; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.DriverService; +import org.onosproject.net.newresource.BandwidthCapacity; import org.onosproject.net.newresource.ResourceAdminService; +import org.slf4j.Logger; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY; +import static org.slf4j.LoggerFactory.getLogger; /** * A class registering resources when they are detected. @@ -47,19 +58,52 @@ public final class ResourceRegistrar { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DeviceService deviceService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigRegistry cfgRegistry; + + private final Logger log = getLogger(getClass()); + + private final List> factories = ImmutableList.of( + new ConfigFactory(CONNECT_POINT_SUBJECT_FACTORY, + BandwidthCapacity.class, BandwidthCapacity.CONFIG_KEY) { + @Override + public BandwidthCapacity createConfig() { + return new BandwidthCapacity(); + } + }); + + private DeviceListener deviceListener; + private final ExecutorService executor = Executors.newSingleThreadExecutor(groupedThreads("onos/resource", "registrar")); + private NetworkConfigListener cfgListener; + @Activate public void activate() { - deviceListener = new ResourceDeviceListener(adminService, deviceService, driverService, executor); + factories.forEach(cfgRegistry::registerConfigFactory); + + cfgListener = new ResourceNetworkConfigListener(adminService, cfgRegistry, executor); + cfgRegistry.addListener(cfgListener); + + deviceListener = new ResourceDeviceListener(adminService, deviceService, driverService, cfgRegistry, executor); deviceService.addListener(deviceListener); + + // TODO Attempt initial registration of existing resources? + + log.info("Started"); } @Deactivate public void deactivate() { deviceService.removeListener(deviceListener); + cfgRegistry.removeListener(cfgListener); + executor.shutdownNow(); + + factories.forEach(cfgRegistry::unregisterConfigFactory); + + log.info("Stopped"); } } diff --git a/tools/package/config/samples/network-cfg-bandwidth.json b/tools/package/config/samples/network-cfg-bandwidth.json new file mode 100644 index 0000000000..f4db24740c --- /dev/null +++ b/tools/package/config/samples/network-cfg-bandwidth.json @@ -0,0 +1,14 @@ +{ + "ports": { + "of:0000000000000002/1": { + "bandwidthCapacity": { + "capacityMbps": 1 + } + }, + "of:0000000000000002/2": { + "bandwidthCapacity": { + "capacityMbps": 2.0 + } + } + } +}