aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/core/net/src
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/core/net/src
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/core/net/src')
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java250
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java281
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java81
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java156
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java282
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java65
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java190
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java38
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java41
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java46
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java175
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java115
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java218
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java288
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java107
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java765
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java173
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java188
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/driver/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/EdgeManager.java241
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/edgeservice/impl/package-info.java4
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java593
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flow/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java416
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FilterTable.java61
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionManager.java439
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionTree.java271
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/FlowObjectiveCompositionUtil.java488
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardTable.java109
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/ForwardUpdateTable.java46
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/NextTable.java61
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/composition/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/flowobjective/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java318
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/group/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java84
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java300
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java288
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/host/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/CompilerRegistry.java128
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentAccumulator.java82
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java254
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCompilationException.java37
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstallationException.java37
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java488
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentProcessor.java46
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/IntentRemovalException.java37
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java457
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTrackerService.java69
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/PathNotFoundException.java46
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/TopologyChangeDelegate.java37
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java152
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java110
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java138
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java91
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompiler.java291
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java151
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java372
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java305
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompiler.java195
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java116
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java104
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java85
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/TwoWayP2PIntentCompiler.java72
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/package-info.java21
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Compiling.java73
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Corrupt.java44
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Failed.java44
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/FinalIntentProcessPhase.java44
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java55
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Installing.java58
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java73
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentWorker.java52
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/PurgeRequest.java70
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java70
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawing.java55
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/Withdrawn.java44
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/intent/impl/phase/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java86
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java343
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/link/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java86
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceLinkListener.java152
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceManager.java148
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceRegistrar.java73
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/newresource/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/PacketManager.java329
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/packet/impl/package-info.java22
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java447
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/proxyarp/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/DeviceResourceManager.java104
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/LinkResourceManager.java293
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/resource/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/StatisticManager.java379
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/statistic/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java287
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/PathManager.java190
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/TopologyManager.java215
-rw-r--r--framework/src/onos/core/net/src/main/java/org/onosproject/net/topology/impl/package-info.java20
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java198
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java168
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java49
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java180
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java48
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java58
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java29
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java132
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java242
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java89
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java331
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java80
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java514
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java640
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java603
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java536
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java76
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java529
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java320
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java97
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java160
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java261
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java285
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java672
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java116
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java326
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java167
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java163
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java188
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java142
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java276
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java147
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java172
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java320
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java149
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java77
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java311
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java667
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java194
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java164
-rw-r--r--framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java215
148 files changed, 25446 insertions, 0 deletions
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
new file mode 100644
index 00000000..161659f9
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.app.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationListener;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStore;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+import org.onosproject.security.SecurityUtil;
+import org.slf4j.Logger;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.security.AppPermission.Type.*;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the application management service.
+ */
+@Component(immediate = true)
+@Service
+public class ApplicationManager
+ extends AbstractListenerManager<ApplicationEvent, ApplicationListener>
+ implements ApplicationService, ApplicationAdminService {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String APP_ID_NULL = "Application ID cannot be null";
+
+ private final ApplicationStoreDelegate delegate = new InternalStoreDelegate();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationStore store;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FeaturesService featuresService;
+
+ private boolean initializing;
+
+ @Activate
+ public void activate() {
+ eventDispatcher.addSink(ApplicationEvent.class, listenerRegistry);
+
+ initializing = true;
+ store.setDelegate(delegate);
+ initializing = false;
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(ApplicationEvent.class);
+ store.unsetDelegate(delegate);
+ log.info("Stopped");
+ }
+
+ @Override
+ public Set<Application> getApplications() {
+ checkPermission(APP_READ);
+ return store.getApplications();
+ }
+
+ @Override
+ public ApplicationId getId(String name) {
+ checkPermission(APP_READ);
+ checkNotNull(name, "Name cannot be null");
+ return store.getId(name);
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ checkPermission(APP_READ);
+ checkNotNull(appId, APP_ID_NULL);
+ return store.getApplication(appId);
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ checkPermission(APP_READ);
+ checkNotNull(appId, APP_ID_NULL);
+ return store.getState(appId);
+ }
+
+ @Override
+ public Set<Permission> getPermissions(ApplicationId appId) {
+ checkPermission(APP_READ);
+ checkNotNull(appId, APP_ID_NULL);
+ return store.getPermissions(appId);
+ }
+
+ @Override
+ public Application install(InputStream appDescStream) {
+ checkNotNull(appDescStream, "Application archive stream cannot be null");
+ Application app = store.create(appDescStream);
+ SecurityUtil.register(app.id());
+ return app;
+ }
+
+ @Override
+ public void uninstall(ApplicationId appId) {
+ checkNotNull(appId, APP_ID_NULL);
+ try {
+ store.remove(appId);
+ } catch (Exception e) {
+ log.warn("Unable to purge application directory for {}", appId.name());
+ }
+ }
+
+ @Override
+ public void activate(ApplicationId appId) {
+ checkNotNull(appId, APP_ID_NULL);
+ if (!SecurityUtil.isAppSecured(appId)) {
+ return;
+ }
+ store.activate(appId);
+ }
+
+ @Override
+ public void deactivate(ApplicationId appId) {
+ checkNotNull(appId, APP_ID_NULL);
+ store.deactivate(appId);
+ }
+
+ @Override
+ public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+ checkNotNull(appId, APP_ID_NULL);
+ checkNotNull(permissions, "Permissions cannot be null");
+ store.setPermissions(appId, permissions);
+ }
+
+ private class InternalStoreDelegate implements ApplicationStoreDelegate {
+ @Override
+ public void notify(ApplicationEvent event) {
+ ApplicationEvent.Type type = event.type();
+ Application app = event.subject();
+ try {
+ if (type == APP_ACTIVATED) {
+ if (installAppFeatures(app)) {
+ log.info("Application {} has been activated", app.id().name());
+ }
+
+ } else if (type == APP_DEACTIVATED) {
+ if (uninstallAppFeatures(app)) {
+ log.info("Application {} has been deactivated", app.id().name());
+ }
+
+ } else if (type == APP_INSTALLED) {
+ if (installAppArtifacts(app)) {
+ log.info("Application {} has been installed", app.id().name());
+ }
+
+ } else if (type == APP_UNINSTALLED) {
+ if (uninstallAppFeatures(app) || uninstallAppArtifacts(app)) {
+ log.info("Application {} has been uninstalled", app.id().name());
+ }
+
+ }
+ post(event);
+
+ } catch (Exception e) {
+ log.warn("Unable to perform operation on application " + app.id().name(), e);
+ }
+ }
+ }
+
+ // The following methods are fully synchronized to guard against remote vs.
+ // locally induced feature service interactions.
+
+ private synchronized boolean installAppArtifacts(Application app) throws Exception {
+ if (app.featuresRepo().isPresent() &&
+ featuresService.getRepository(app.featuresRepo().get()) == null) {
+ featuresService.addRepository(app.featuresRepo().get());
+ return true;
+ }
+ return false;
+ }
+
+ private synchronized boolean uninstallAppArtifacts(Application app) throws Exception {
+ if (app.featuresRepo().isPresent() &&
+ featuresService.getRepository(app.featuresRepo().get()) != null) {
+ featuresService.removeRepository(app.featuresRepo().get());
+ return true;
+ }
+ return false;
+ }
+
+ private synchronized boolean installAppFeatures(Application app) throws Exception {
+ boolean changed = false;
+ for (String name : app.features()) {
+ Feature feature = featuresService.getFeature(name);
+ if (feature != null && !featuresService.isInstalled(feature)) {
+ featuresService.installFeature(name);
+ changed = true;
+ } else if (feature == null && !initializing) {
+ // Suppress feature-not-found reporting during startup since these
+ // can arise naturally from the staggered cluster install.
+ log.warn("Feature {} not found", name);
+ }
+ }
+ return changed;
+ }
+
+ private synchronized boolean uninstallAppFeatures(Application app) throws Exception {
+ boolean changed = false;
+ for (String name : app.features()) {
+ Feature feature = featuresService.getFeature(name);
+ if (feature != null && featuresService.isInstalled(feature)) {
+ featuresService.uninstallFeature(name);
+ changed = true;
+ } else if (feature == null) {
+ log.warn("Feature {} not found", name);
+ }
+ }
+ return changed;
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java
new file mode 100644
index 00000000..d5f30374
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Subsystem for managing applications.
+ */
+package org.onosproject.app.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java
new file mode 100644
index 00000000..1933ee55
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ComponentConfigManager.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cfg.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.AbstractAccumulator;
+import org.onlab.util.Accumulator;
+import org.onlab.util.SharedExecutors;
+import org.onosproject.cfg.ComponentConfigEvent;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cfg.ComponentConfigStore;
+import org.onosproject.cfg.ComponentConfigStoreDelegate;
+import org.onosproject.cfg.ConfigProperty;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.security.AppPermission.Type.*;
+
+
+/**
+ * Implementation of the centralized component configuration service.
+ */
+@Component(immediate = true)
+@Service
+public class ComponentConfigManager implements ComponentConfigService {
+
+ private static final String COMPONENT_NULL = "Component name cannot be null";
+ private static final String PROPERTY_NULL = "Property name cannot be null";
+
+ //Symbolic constants for use with the accumulator
+ private static final int MAX_ITEMS = 100;
+ private static final int MAX_BATCH_MILLIS = 1000;
+ private static final int MAX_IDLE_MILLIS = 250;
+
+ private static final String RESOURCE_EXT = ".cfgdef";
+
+ private final Logger log = getLogger(getClass());
+
+ private final ComponentConfigStoreDelegate delegate = new InternalStoreDelegate();
+ private final InternalAccumulator accumulator = new InternalAccumulator();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigStore store;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ConfigurationAdmin cfgAdmin;
+
+ // Locally maintained catalog of definitions.
+ private final Map<String, Map<String, ConfigProperty>> properties =
+ Maps.newConcurrentMap();
+
+
+ @Activate
+ public void activate() {
+ store.setDelegate(delegate);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ store.unsetDelegate(delegate);
+ log.info("Stopped");
+ }
+
+ @Override
+ public Set<String> getComponentNames() {
+ checkPermission(CONFIG_READ);
+
+ return ImmutableSet.copyOf(properties.keySet());
+ }
+
+ @Override
+ public void registerProperties(Class<?> componentClass) {
+ checkPermission(CONFIG_WRITE);
+
+ String componentName = componentClass.getName();
+ String resourceName = componentClass.getSimpleName() + RESOURCE_EXT;
+ try (InputStream ris = componentClass.getResourceAsStream(resourceName)) {
+ checkArgument(ris != null, "Property definitions not found at resource %s",
+ resourceName);
+
+ // Read the definitions
+ Set<ConfigProperty> defs = ConfigPropertyDefinitions.read(ris);
+
+ // Produce a new map of the properties and register it.
+ Map<String, ConfigProperty> map = Maps.newConcurrentMap();
+ defs.forEach(p -> map.put(p.name(), p));
+
+ properties.put(componentName, map);
+ loadExistingValues(componentName);
+ } catch (IOException e) {
+ log.error("Unable to read property definitions from resource " + resourceName, e);
+ }
+ }
+
+ @Override
+ public void unregisterProperties(Class<?> componentClass, boolean clear) {
+ checkPermission(CONFIG_WRITE);
+
+ String componentName = componentClass.getName();
+ checkNotNull(componentName, COMPONENT_NULL);
+ Map<String, ConfigProperty> cps = properties.remove(componentName);
+ if (clear && cps != null) {
+ cps.keySet().forEach(name -> store.unsetProperty(componentName, name));
+ clearExistingValues(componentName);
+ }
+ }
+
+ // Clears any existing values that may have been set.
+ private void clearExistingValues(String componentName) {
+ triggerUpdate(componentName);
+ }
+
+ @Override
+ public Set<ConfigProperty> getProperties(String componentName) {
+ checkPermission(CONFIG_READ);
+
+ Map<String, ConfigProperty> map = properties.get(componentName);
+ return map != null ? ImmutableSet.copyOf(map.values()) : null;
+ }
+
+ @Override
+ public void setProperty(String componentName, String name, String value) {
+ checkPermission(CONFIG_WRITE);
+
+ checkNotNull(componentName, COMPONENT_NULL);
+ checkNotNull(name, PROPERTY_NULL);
+ store.setProperty(componentName, name, value);
+ }
+
+ @Override
+ public void unsetProperty(String componentName, String name) {
+ checkPermission(CONFIG_WRITE);
+
+ checkNotNull(componentName, COMPONENT_NULL);
+ checkNotNull(name, PROPERTY_NULL);
+ store.unsetProperty(componentName, name);
+ }
+
+ private class InternalStoreDelegate implements ComponentConfigStoreDelegate {
+
+ @Override
+ public void notify(ComponentConfigEvent event) {
+ String componentName = event.subject();
+ String name = event.name();
+ String value = event.value();
+
+ switch (event.type()) {
+ case PROPERTY_SET:
+ set(componentName, name, value);
+ break;
+ case PROPERTY_UNSET:
+ reset(componentName, name);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // Buffers multiple subsequent configuration updates into one notification.
+ private class InternalAccumulator extends AbstractAccumulator<String>
+ implements Accumulator<String> {
+
+ protected InternalAccumulator() {
+ super(SharedExecutors.getTimer(), MAX_ITEMS, MAX_BATCH_MILLIS, MAX_IDLE_MILLIS);
+ }
+
+ @Override
+ public void processItems(List<String> items) {
+ // Conversion to set removes duplicates
+ Set<String> componentSet = new HashSet<>(items);
+ componentSet.forEach(ComponentConfigManager.this::triggerUpdate);
+ }
+ }
+
+ // Locates the property in the component map and replaces it with an
+ // updated copy.
+ private void set(String componentName, String name, String value) {
+ Map<String, ConfigProperty> map = properties.get(componentName);
+ if (map != null) {
+ ConfigProperty prop = map.get(name);
+ if (prop != null) {
+ map.put(name, ConfigProperty.setProperty(prop, value));
+ accumulator.add(componentName);
+ return;
+ }
+ }
+ log.warn("Unable to set non-existent property {} for component {}",
+ name, componentName);
+ }
+
+ // Locates the property in the component map and replaces it with an
+ // reset copy.
+ private void reset(String componentName, String name) {
+ Map<String, ConfigProperty> map = properties.get(componentName);
+ if (map != null) {
+ ConfigProperty prop = map.get(name);
+ if (prop != null) {
+ map.put(name, ConfigProperty.resetProperty(prop));
+ accumulator.add(componentName);
+ return;
+ }
+ log.warn("Unable to reset non-existent property {} for component {}",
+ name, componentName);
+ }
+ }
+
+ // Loads existing property values that may have been set.
+ private void loadExistingValues(String componentName) {
+ try {
+ Configuration cfg = cfgAdmin.getConfiguration(componentName, null);
+ Map<String, ConfigProperty> map = properties.get(componentName);
+ Dictionary<String, Object> props = cfg.getProperties();
+ if (props != null) {
+ Enumeration<String> it = props.keys();
+ while (it.hasMoreElements()) {
+ String name = it.nextElement();
+ ConfigProperty p = map.get(name);
+ if (p != null) {
+ map.put(name, ConfigProperty.setProperty(p, (String) props.get(name)));
+ }
+ }
+ }
+ } catch (IOException e) {
+ log.error("Unable to get configuration for " + componentName, e);
+ }
+
+ }
+
+ // FIXME: This should be a slightly deferred execution to allow changing
+ // values just once per component when a number of updates arrive shortly
+ // after each other.
+ private void triggerUpdate(String componentName) {
+ try {
+ Configuration cfg = cfgAdmin.getConfiguration(componentName, null);
+ Map<String, ConfigProperty> map = properties.get(componentName);
+ Dictionary<String, Object> props = new Hashtable<>();
+ map.values().forEach(p -> props.put(p.name(), p.value()));
+ cfg.update(props);
+ } catch (IOException e) {
+ log.warn("Unable to update configuration for " + componentName, e);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java
new file mode 100644
index 00000000..0f416c76
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/ConfigPropertyDefinitions.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cfg.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.cfg.ConfigProperty;
+import org.onosproject.cfg.ConfigProperty.Type;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Set;
+
+import static org.onosproject.cfg.ConfigProperty.defineProperty;
+
+/**
+ * Utility for writing and reading configuration property definition file.
+ */
+public final class ConfigPropertyDefinitions {
+
+ private static final String FMT = "%s|%s|%s|%s\n";
+ private static final String SEP = "\\|";
+ private static final String COMMENT = "#";
+
+ private ConfigPropertyDefinitions() {
+ }
+
+ /**
+ * Writes the specified set of property definitions into the given output
+ * stream.
+ *
+ * @param stream output stream
+ * @param props properties whose definitions are to be written
+ * @throws java.io.IOException if unable to write the stream
+ */
+ public static void write(OutputStream stream, Set<ConfigProperty> props) throws IOException {
+ try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(stream))) {
+ props.forEach(p -> pw.format(FMT, p.name(), p.type(), p.description(), p.defaultValue()));
+ }
+ }
+
+ /**
+ * Reads the specified input stream and creates from its contents a
+ * set of property definitions.
+ *
+ * @param stream input stream
+ * @return properties whose definitions are contained in the stream
+ * @throws java.io.IOException if unable to read the stream
+ */
+ public static Set<ConfigProperty> read(InputStream stream) throws IOException {
+ ImmutableSet.Builder<ConfigProperty> builder = ImmutableSet.builder();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ if (!line.isEmpty() && !line.startsWith(COMMENT)) {
+ String[] f = line.split(SEP, 4);
+ builder.add(defineProperty(f[0], Type.valueOf(f[1]), f[2], f[3]));
+ }
+ }
+ }
+ return builder.build();
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java
new file mode 100644
index 00000000..4f76c31c
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cfg/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Subsystem for central management of component configurations.
+ */
+package org.onosproject.cfg.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
new file mode 100644
index 00000000..04d1dfdf
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cluster.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.karaf.system.SystemService;
+import org.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.ClusterDefinitionService;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ClusterStore;
+import org.onosproject.cluster.ClusterStoreDelegate;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.event.AbstractListenerManager;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.security.AppPermission.Type.*;
+
+
+
+/**
+ * Implementation of the cluster service.
+ */
+@Component(immediate = true)
+@Service
+public class ClusterManager
+ extends AbstractListenerManager<ClusterEvent, ClusterEventListener>
+ implements ClusterService, ClusterAdminService {
+
+ public static final String INSTANCE_ID_NULL = "Instance ID cannot be null";
+ private final Logger log = getLogger(getClass());
+
+ private ClusterStoreDelegate delegate = new InternalStoreDelegate();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterDefinitionService clusterDefinitionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterStore store;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected SystemService systemService;
+
+ @Activate
+ public void activate() {
+ store.setDelegate(delegate);
+ eventDispatcher.addSink(ClusterEvent.class, listenerRegistry);
+ clusterDefinitionService.seedNodes()
+ .forEach(node -> store.addNode(node.id(), node.ip(), node.tcpPort()));
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ store.unsetDelegate(delegate);
+ eventDispatcher.removeSink(ClusterEvent.class);
+ log.info("Stopped");
+ }
+
+ @Override
+ public ControllerNode getLocalNode() {
+ checkPermission(CLUSTER_READ);
+ return store.getLocalNode();
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ checkPermission(CLUSTER_READ);
+ return store.getNodes();
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ checkPermission(CLUSTER_READ);
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ return store.getNode(nodeId);
+ }
+
+ @Override
+ public ControllerNode.State getState(NodeId nodeId) {
+ checkPermission(CLUSTER_READ);
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ return store.getState(nodeId);
+ }
+
+
+ @Override
+ public DateTime getLastUpdated(NodeId nodeId) {
+ checkPermission(CLUSTER_READ);
+ return store.getLastUpdated(nodeId);
+ }
+
+ @Override
+ public void formCluster(Set<ControllerNode> nodes, String ipPrefix) {
+ checkNotNull(nodes, "Nodes cannot be null");
+ checkArgument(!nodes.isEmpty(), "Nodes cannot be empty");
+ checkNotNull(ipPrefix, "IP prefix cannot be null");
+ clusterDefinitionService.formCluster(nodes, ipPrefix);
+ try {
+ log.warn("Shutting down container for cluster reconfiguration!");
+ systemService.reboot("now", SystemService.Swipe.NONE);
+ } catch (Exception e) {
+ log.error("Unable to reboot container", e);
+ }
+ }
+
+ @Override
+ public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ checkNotNull(ip, "IP address cannot be null");
+ checkArgument(tcpPort > 5000, "TCP port must be > 5000");
+ return store.addNode(nodeId, ip, tcpPort);
+ }
+
+ @Override
+ public void removeNode(NodeId nodeId) {
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ store.removeNode(nodeId);
+ }
+
+ // Store delegate to re-post events emitted from the store.
+ private class InternalStoreDelegate implements ClusterStoreDelegate {
+ @Override
+ public void notify(ClusterEvent event) {
+ post(event);
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java
new file mode 100644
index 00000000..56d369fd
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/MastershipManager.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cluster.impl;
+
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.Timer.Context;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.metrics.MetricsService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.core.MetricsHelper;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipStore;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Lists.newArrayList;
+import static org.onlab.metrics.MetricsUtil.startTimer;
+import static org.onlab.metrics.MetricsUtil.stopTimer;
+import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
+import static org.onosproject.net.MastershipRole.MASTER;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.security.AppPermission.Type.*;
+
+
+
+@Component(immediate = true)
+@Service
+public class MastershipManager
+ extends AbstractListenerManager<MastershipEvent, MastershipListener>
+ implements MastershipService, MastershipAdminService, MastershipTermService,
+ MetricsHelper {
+
+ private static final String NODE_ID_NULL = "Node ID cannot be null";
+ private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+ private static final String ROLE_NULL = "Mastership role cannot be null";
+
+ private final Logger log = getLogger(getClass());
+
+ private final MastershipStoreDelegate delegate = new InternalDelegate();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipStore store;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MetricsService metricsService;
+
+ private NodeId localNodeId;
+ private Timer requestRoleTimer;
+
+ @Activate
+ public void activate() {
+ requestRoleTimer = createTimer("Mastership", "requestRole", "responseTime");
+ localNodeId = clusterService.getLocalNode().id();
+ eventDispatcher.addSink(MastershipEvent.class, listenerRegistry);
+ store.setDelegate(delegate);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(MastershipEvent.class);
+ store.unsetDelegate(delegate);
+ log.info("Stopped");
+ }
+
+ @Override
+ public CompletableFuture<Void> setRole(NodeId nodeId, DeviceId deviceId, MastershipRole role) {
+ checkNotNull(nodeId, NODE_ID_NULL);
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ checkNotNull(role, ROLE_NULL);
+
+ CompletableFuture<MastershipEvent> eventFuture = null;
+
+ switch (role) {
+ case MASTER:
+ eventFuture = store.setMaster(nodeId, deviceId);
+ break;
+ case STANDBY:
+ eventFuture = store.setStandby(nodeId, deviceId);
+ break;
+ case NONE:
+ eventFuture = store.relinquishRole(nodeId, deviceId);
+ break;
+ default:
+ log.info("Unknown role; ignoring");
+ return CompletableFuture.completedFuture(null);
+ }
+
+ return eventFuture.thenAccept(this::post)
+ .thenApply(v -> null);
+ }
+
+ @Override
+ public MastershipRole getLocalRole(DeviceId deviceId) {
+ checkPermission(CLUSTER_READ);
+
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return store.getRole(clusterService.getLocalNode().id(), deviceId);
+ }
+
+ @Override
+ public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) {
+ checkPermission(CLUSTER_WRITE);
+ return store.relinquishRole(localNodeId, deviceId)
+ .thenAccept(this::post)
+ .thenApply(v -> null);
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+ checkPermission(CLUSTER_WRITE);
+
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ final Context timer = startTimer(requestRoleTimer);
+ return store.requestRole(deviceId).whenComplete((result, error) -> stopTimer(timer));
+
+ }
+
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ checkPermission(CLUSTER_READ);
+
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return store.getMaster(deviceId);
+ }
+
+ @Override
+ public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+ checkPermission(CLUSTER_READ);
+
+ checkNotNull(nodeId, NODE_ID_NULL);
+ return store.getDevices(nodeId);
+ }
+
+ @Override
+ public RoleInfo getNodesFor(DeviceId deviceId) {
+ checkPermission(CLUSTER_READ);
+
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return store.getNodes(deviceId);
+ }
+
+ @Override
+ public MastershipTerm getMastershipTerm(DeviceId deviceId) {
+ return store.getTermFor(deviceId);
+ }
+
+ @Override
+ public MetricsService metricsService() {
+ return metricsService;
+ }
+
+ @Override
+ public void balanceRoles() {
+ List<ControllerNode> nodes = newArrayList(clusterService.getNodes());
+ Map<ControllerNode, Set<DeviceId>> controllerDevices = new HashMap<>();
+ int deviceCount = 0;
+
+ // Create buckets reflecting current ownership.
+ for (ControllerNode node : nodes) {
+ if (clusterService.getState(node.id()) == ACTIVE) {
+ Set<DeviceId> devicesOf = new HashSet<>(getDevicesOf(node.id()));
+ deviceCount += devicesOf.size();
+ controllerDevices.put(node, devicesOf);
+ log.info("Node {} has {} devices.", node.id(), devicesOf.size());
+ }
+ }
+
+ // Now re-balance the buckets until they are roughly even.
+ List<CompletableFuture<Void>> balanceBucketsFutures = Lists.newLinkedList();
+ int rounds = controllerDevices.keySet().size();
+ for (int i = 0; i < rounds; i++) {
+ // Iterate over the buckets and find the smallest and the largest.
+ ControllerNode smallest = findBucket(true, controllerDevices);
+ ControllerNode largest = findBucket(false, controllerDevices);
+ balanceBucketsFutures.add(balanceBuckets(smallest, largest, controllerDevices, deviceCount));
+ }
+ CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf(
+ balanceBucketsFutures.toArray(new CompletableFuture[balanceBucketsFutures.size()]));
+
+ Futures.getUnchecked(balanceRolesFuture);
+ }
+
+ private ControllerNode findBucket(boolean min,
+ Map<ControllerNode, Set<DeviceId>> controllerDevices) {
+ int xSize = min ? Integer.MAX_VALUE : -1;
+ ControllerNode xNode = null;
+ for (ControllerNode node : controllerDevices.keySet()) {
+ int size = controllerDevices.get(node).size();
+ if ((min && size < xSize) || (!min && size > xSize)) {
+ xSize = size;
+ xNode = node;
+ }
+ }
+ return xNode;
+ }
+
+ private CompletableFuture<Void> balanceBuckets(ControllerNode smallest, ControllerNode largest,
+ Map<ControllerNode, Set<DeviceId>> controllerDevices,
+ int deviceCount) {
+ Collection<DeviceId> minBucket = controllerDevices.get(smallest);
+ Collection<DeviceId> maxBucket = controllerDevices.get(largest);
+ int bucketCount = controllerDevices.keySet().size();
+
+ int delta = (maxBucket.size() - minBucket.size()) / 2;
+ delta = Math.min(deviceCount / bucketCount, delta);
+
+ List<CompletableFuture<Void>> setRoleFutures = Lists.newLinkedList();
+
+ if (delta > 0) {
+ log.info("Attempting to move {} nodes from {} to {}...", delta,
+ largest.id(), smallest.id());
+
+ int i = 0;
+ Iterator<DeviceId> it = maxBucket.iterator();
+ while (it.hasNext() && i < delta) {
+ DeviceId deviceId = it.next();
+ log.info("Setting {} as the master for {}", smallest.id(), deviceId);
+ setRoleFutures.add(setRole(smallest.id(), deviceId, MASTER));
+ controllerDevices.get(smallest).add(deviceId);
+ it.remove();
+ i++;
+ }
+ }
+
+ return CompletableFuture.allOf(setRoleFutures.toArray(new CompletableFuture[setRoleFutures.size()]));
+ }
+
+
+ public class InternalDelegate implements MastershipStoreDelegate {
+ @Override
+ public void notify(MastershipEvent event) {
+ post(event);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java
new file mode 100644
index 00000000..653edaa5
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/cluster/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Subsystem for tracking controller cluster nodes.
+ */
+package org.onosproject.cluster.impl;
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java
new file mode 100644
index 00000000..267cd713
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/BlockAllocatorBasedIdGenerator.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.onosproject.core.IdBlock;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.core.UnavailableIdException;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base class of {@link IdGenerator} implementations which use {@link IdBlockAllocator} as
+ * backend.
+ */
+public class BlockAllocatorBasedIdGenerator implements IdGenerator {
+ protected final IdBlockAllocator allocator;
+ protected IdBlock idBlock;
+ protected AtomicBoolean initialized;
+
+
+ /**
+ * Constructs an ID generator which use {@link IdBlockAllocator} as backend.
+ *
+ * @param allocator the ID block allocator to use
+ */
+ protected BlockAllocatorBasedIdGenerator(IdBlockAllocator allocator) {
+ this.allocator = checkNotNull(allocator, "allocator cannot be null");
+ this.initialized = new AtomicBoolean(false);
+ }
+
+ @Override
+ public long getNewId() {
+ try {
+ if (!initialized.get()) {
+ synchronized (allocator) {
+ if (!initialized.get()) {
+ idBlock = allocator.allocateUniqueIdBlock();
+ initialized.set(true);
+ }
+ }
+ }
+ return idBlock.getNextId();
+ } catch (UnavailableIdException e) {
+ synchronized (allocator) {
+ idBlock = allocator.allocateUniqueIdBlock();
+ }
+ return idBlock.getNextId();
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java
new file mode 100644
index 00000000..07612292
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.SharedExecutors;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdBlockStore;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.core.Version;
+import org.onosproject.event.EventDeliveryService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.onosproject.security.AppPermission.Type.*;
+
+
+
+/**
+ * Core service implementation.
+ */
+@Component(immediate = true)
+@Service
+public class CoreManager implements CoreService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final File VERSION_FILE = new File("../VERSION");
+ private static Version version = Version.version("1.3.0-SNAPSHOT");
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationIdStore applicationIdStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IdBlockStore idBlockStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService cfgService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDeliveryService;
+
+ private static final int DEFAULT_POOL_SIZE = 30;
+ @Property(name = "sharedThreadPoolSize", intValue = DEFAULT_POOL_SIZE,
+ label = "Configure shared pool maximum size ")
+ private int sharedThreadPoolSize = DEFAULT_POOL_SIZE;
+
+ private static final int DEFAULT_EVENT_TIME = 2000;
+ @Property(name = "maxEventTimeLimit", intValue = DEFAULT_EVENT_TIME,
+ label = "Maximum number of millis an event sink has to process an event")
+ private int maxEventTimeLimit = DEFAULT_EVENT_TIME;
+
+ @Activate
+ public void activate() {
+ registerApplication(CORE_APP_NAME);
+ cfgService.registerProperties(getClass());
+ List<String> versionLines = Tools.slurp(VERSION_FILE);
+ if (versionLines != null && !versionLines.isEmpty()) {
+ version = Version.version(versionLines.get(0));
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ cfgService.unregisterProperties(getClass(), false);
+ SharedExecutors.shutdown();
+ }
+
+ @Override
+ public Version version() {
+ checkPermission(APP_READ);
+
+ return version;
+ }
+
+ @Override
+ public Set<ApplicationId> getAppIds() {
+ checkPermission(APP_READ);
+
+ return applicationIdStore.getAppIds();
+ }
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ checkPermission(APP_READ);
+
+ return applicationIdStore.getAppId(id);
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ checkPermission(APP_READ);
+
+ return applicationIdStore.getAppId(name);
+ }
+
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ checkNotNull(name, "Application ID cannot be null");
+ return applicationIdStore.registerApplication(name);
+ }
+
+ @Override
+ public IdGenerator getIdGenerator(String topic) {
+ IdBlockAllocator allocator = new StoreBasedIdBlockAllocator(topic, idBlockStore);
+ return new BlockAllocatorBasedIdGenerator(allocator);
+ }
+
+
+ @Modified
+ public void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Integer poolSize = getIntegerProperty(properties, "sharedThreadPoolSize");
+
+ if (poolSize != null && poolSize > 1) {
+ sharedThreadPoolSize = poolSize;
+ SharedExecutors.setPoolSize(sharedThreadPoolSize);
+ } else if (poolSize != null) {
+ log.warn("sharedThreadPoolSize must be greater than 1");
+ }
+
+ Integer timeLimit = getIntegerProperty(properties, "maxEventTimeLimit");
+ if (timeLimit != null && timeLimit > 1) {
+ maxEventTimeLimit = timeLimit;
+ eventDeliveryService.setDispatchTimeLimit(maxEventTimeLimit);
+ } else if (timeLimit != null) {
+ log.warn("maxEventTimeLimit must be greater than 1");
+ }
+
+ log.info("Settings: sharedThreadPoolSize={}, maxEventTimeLimit={}",
+ sharedThreadPoolSize, maxEventTimeLimit);
+ }
+
+
+ /**
+ * Get Integer property from the propertyName
+ * Return null if propertyName is not found.
+ *
+ * @param properties properties to be looked up
+ * @param propertyName the name of the property to look up
+ * @return value when the propertyName is defined or return null
+ */
+ private static Integer getIntegerProperty(Dictionary<?, ?> properties,
+ String propertyName) {
+ Integer value = null;
+ try {
+ String s = (String) properties.get(propertyName);
+ value = isNullOrEmpty(s) ? value : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ value = null;
+ }
+ return value;
+ }
+
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java
new file mode 100644
index 00000000..ba8594f2
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/IdBlockAllocator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.onosproject.core.IdBlock;
+
+/**
+ * An interface that gives unique ID spaces.
+ */
+public interface IdBlockAllocator {
+ /**
+ * Allocates a unique Id Block.
+ *
+ * @return Id Block.
+ */
+ IdBlock allocateUniqueIdBlock();
+
+ /**
+ * Allocates next unique id and retrieve a new range of ids if needed.
+ *
+ * @param range range to use for the identifier
+ * @return Id Block.
+ */
+ IdBlock allocateUniqueIdBlock(long range);
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java
new file mode 100644
index 00000000..5a3f08df
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/MetricsManagerComponent.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+
+import org.onlab.metrics.MetricsManager;
+
+/**
+ * Metrics service implementation.
+ */
+@Component(immediate = true)
+@Service
+public class MetricsManagerComponent extends MetricsManager {
+
+ @Activate
+ protected void activate() {
+ super.clear();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.clear();
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java
new file mode 100644
index 00000000..c28de73d
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/StoreBasedIdBlockAllocator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.onosproject.core.IdBlock;
+import org.onosproject.core.IdBlockStore;
+
+public class StoreBasedIdBlockAllocator implements IdBlockAllocator {
+ private final IdBlockStore store;
+ private final String topic;
+
+ public StoreBasedIdBlockAllocator(String topic, IdBlockStore store) {
+ this.topic = topic;
+ this.store = store;
+ }
+
+ /**
+ * Returns a block of IDs which are unique and unused.
+ * Range of IDs is fixed size and is assigned incrementally as this method
+ * called.
+ *
+ * @return an IdBlock containing a set of unique IDs
+ */
+ @Override
+ public synchronized IdBlock allocateUniqueIdBlock() {
+ return store.getIdBlock(topic);
+ }
+
+ @Override
+ public IdBlock allocateUniqueIdBlock(long range) {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java
new file mode 100644
index 00000000..f8c3e672
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/core/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Miscellaneous core system implementations.
+ */
+package org.onosproject.core.impl;
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java
new file mode 100644
index 00000000..79ce74b7
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/CoreEventDispatcher.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.event.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.SharedExecutors;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
+import org.slf4j.Logger;
+
+import java.util.TimerTask;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple implementation of an event dispatching service.
+ */
+@Component(immediate = true)
+@Service
+public class CoreEventDispatcher extends DefaultEventSinkRegistry
+ implements EventDeliveryService {
+
+ private final Logger log = getLogger(getClass());
+
+ // Default number of millis a sink can take to process an event.
+ private static final long DEFAULT_EXECUTE_MS = 5_000; // ms
+ private static final long WATCHDOG_MS = 250; // ms
+
+ private final BlockingQueue<Event> events = new LinkedBlockingQueue<>();
+
+ private final ExecutorService executor =
+ newSingleThreadExecutor(groupedThreads("onos/event", "dispatch-%d"));
+
+ @SuppressWarnings("unchecked")
+ private static final Event KILL_PILL = new AbstractEvent(null, 0) {
+ };
+
+ private DispatchLoop dispatchLoop;
+ private long maxProcessMillis = DEFAULT_EXECUTE_MS;
+
+ // Means to detect long-running sinks
+ private TimerTask watchdog;
+ private EventSink lastSink;
+ private long lastStart = 0;
+ private Future<?> dispatchFuture;
+
+ @Override
+ public void post(Event event) {
+ if (!events.add(event)) {
+ log.error("Unable to post event {}", event);
+ }
+ }
+
+ @Activate
+ public void activate() {
+ dispatchLoop = new DispatchLoop();
+ dispatchFuture = executor.submit(dispatchLoop);
+ watchdog = new Watchdog();
+ SharedExecutors.getTimer().schedule(watchdog, WATCHDOG_MS, WATCHDOG_MS);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ dispatchLoop.stop();
+ watchdog.cancel();
+ post(KILL_PILL);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void setDispatchTimeLimit(long millis) {
+ checkArgument(millis >= WATCHDOG_MS,
+ "Time limit must be greater than %s", WATCHDOG_MS);
+ maxProcessMillis = millis;
+ }
+
+ @Override
+ public long getDispatchTimeLimit() {
+ return maxProcessMillis;
+ }
+
+ // Auxiliary event dispatching loop that feeds off the events queue.
+ private class DispatchLoop implements Runnable {
+ private volatile boolean stopped;
+
+ @Override
+ public void run() {
+ stopped = false;
+ log.info("Dispatch loop initiated");
+ while (!stopped) {
+ try {
+ // Fetch the next event and if it is the kill-pill, bail
+ Event event = events.take();
+ if (event == KILL_PILL) {
+ break;
+ }
+ process(event);
+ } catch (InterruptedException e) {
+ log.warn("Dispatch loop interrupted");
+ } catch (Exception e) {
+ log.warn("Error encountered while dispatching event:", e);
+ }
+ }
+ log.info("Dispatch loop terminated");
+ }
+
+ // Locate the sink for the event class and use it to process the event
+ @SuppressWarnings("unchecked")
+ private void process(Event event) {
+ EventSink sink = getSink(event.getClass());
+ if (sink != null) {
+ lastSink = sink;
+ lastStart = System.currentTimeMillis();
+ sink.process(event);
+ lastStart = 0;
+ } else {
+ log.warn("No sink registered for event class {}",
+ event.getClass().getName());
+ }
+ }
+
+ void stop() {
+ stopped = true;
+ }
+ }
+
+ // Monitors event sinks to make sure none take too long to execute.
+ private class Watchdog extends TimerTask {
+ @Override
+ public void run() {
+ long delta = System.currentTimeMillis() - lastStart;
+ if (lastStart > 0 && delta > maxProcessMillis) {
+ lastStart = 0;
+ log.warn("Event sink {} exceeded execution time limit: {} ms; spawning new dispatch loop",
+ lastSink.getClass().getName(), delta);
+
+ // Notify the sink that it has exceeded its time limit.
+ lastSink.onProcessLimit();
+
+ // Cancel the old dispatch loop and submit a new one.
+ dispatchLoop.stop();
+ dispatchLoop = new DispatchLoop();
+ dispatchFuture.cancel(true);
+ dispatchFuture = executor.submit(dispatchLoop);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java
new file mode 100644
index 00000000..f0882f3d
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/event/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Local event dispatching mechanism.
+ */
+package org.onosproject.event.impl;
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
new file mode 100644
index 00000000..de2f5c3b
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.config.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.config.basics.InterfaceConfig;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
+import org.onosproject.net.config.basics.BasicHostConfig;
+import org.onosproject.net.config.basics.BasicLinkConfig;
+import org.onosproject.net.config.basics.OpticalPortConfig;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+import static org.onosproject.net.config.basics.SubjectFactories.*;
+
+/**
+ * Component for registration of builtin basic network configurations.
+ */
+@Component(immediate = true)
+public class BasicNetworkConfigs {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final Set<ConfigFactory> factories = ImmutableSet.of(
+ new ConfigFactory<DeviceId, BasicDeviceConfig>(DEVICE_SUBJECT_FACTORY,
+ BasicDeviceConfig.class,
+ "basic") {
+ @Override
+ public BasicDeviceConfig createConfig() {
+ return new BasicDeviceConfig();
+ }
+ },
+ new ConfigFactory<ConnectPoint, InterfaceConfig>(CONNECT_POINT_SUBJECT_FACTORY,
+ InterfaceConfig.class,
+ "interfaces",
+ true) {
+ @Override
+ public InterfaceConfig createConfig() {
+ return new InterfaceConfig();
+ }
+ },
+ new ConfigFactory<HostId, BasicHostConfig>(HOST_SUBJECT_FACTORY,
+ BasicHostConfig.class,
+ "basic") {
+ @Override
+ public BasicHostConfig createConfig() {
+ return new BasicHostConfig();
+ }
+ },
+ new ConfigFactory<LinkKey, BasicLinkConfig>(LINK_SUBJECT_FACTORY,
+ BasicLinkConfig.class,
+ "basic") {
+ @Override
+ public BasicLinkConfig createConfig() {
+ return new BasicLinkConfig();
+ }
+ },
+ new ConfigFactory<ConnectPoint, OpticalPortConfig>(CONNECT_POINT_SUBJECT_FACTORY,
+ OpticalPortConfig.class,
+ "optical") {
+ @Override
+ public OpticalPortConfig createConfig() {
+ return new OpticalPortConfig();
+ }
+ }
+ );
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry registry;
+
+ @Activate
+ public void activate() {
+ SubjectFactories.setCoreService(coreService);
+ factories.forEach(registry::registerConfigFactory);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ factories.forEach(registry::unregisterConfigFactory);
+ log.info("Stopped");
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java
new file mode 100644
index 00000000..01348c15
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigLoader.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.config.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Component for loading the initial network configuration.
+ */
+@Component(immediate = true)
+public class NetworkConfigLoader {
+
+ private static final File CFG_FILE = new File("../config/network-cfg.json");
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigService networkConfigService;
+
+ // FIXME: Add mutual exclusion to make sure this happens only once per startup.
+
+ private final Map<InnerConfigPosition, JsonNode> jsons = Maps.newConcurrentMap();
+
+ private final NetworkConfigListener configListener = new InnerConfigListener();
+
+ private ObjectNode root;
+
+ @Activate
+ public void activate() {
+ //TODO Maybe this should be at the bottom to avoid a potential race
+ networkConfigService.addListener(configListener);
+ try {
+ if (CFG_FILE.exists()) {
+ root = (ObjectNode) new ObjectMapper().readTree(CFG_FILE);
+
+ populateConfigurations();
+
+ applyConfigurations();
+
+ log.info("Loaded initial network configuration from {}", CFG_FILE);
+ }
+ } catch (Exception e) {
+ log.warn("Unable to load initial network configuration from {}",
+ CFG_FILE, e);
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ networkConfigService.removeListener(configListener);
+ }
+ // sweep through pending config jsons and try to add them
+
+ /**
+ * Inner class that allows for handling of newly added NetConfig types.
+ */
+ private final class InnerConfigListener implements NetworkConfigListener {
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+ //TODO should this be done for other types of NetworkConfigEvents?
+ if (event.type() == NetworkConfigEvent.Type.CONFIG_REGISTERED ||
+ event.type() == NetworkConfigEvent.Type.CONFIG_ADDED) {
+ applyConfigurations();
+ }
+
+ }
+ }
+
+ /**
+ * Inner class that allows for tracking of JSON class configurations.
+ */
+ private final class InnerConfigPosition {
+ private final String subjectKey, subject, configKey;
+
+ private String subjectKey() {
+ return subjectKey;
+ }
+
+ private String subject() {
+ return subject;
+ }
+
+ private String configKey() {
+ return configKey;
+ }
+
+ private InnerConfigPosition(String subjectKey, String subject, String configKey) {
+ this.subjectKey = subjectKey;
+ this.subject = subject;
+ this.configKey = configKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof InnerConfigPosition) {
+ final InnerConfigPosition that = (InnerConfigPosition) obj;
+ return Objects.equals(this.subjectKey, that.subjectKey)
+ && Objects.equals(this.subject, that.subject)
+ && Objects.equals(this.configKey, that.configKey);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subjectKey, subject, configKey);
+ }
+ }
+
+ /**
+ * Save the JSON leaves associated with a specific subject key.
+ *
+ * @param sk the subject key string.
+ * @param node the node associated with the subject key.
+ */
+ private void saveJson(String sk, ObjectNode node) {
+ node.fieldNames().forEachRemaining(s ->
+ saveSubjectJson(sk, s, (ObjectNode) node.path(s)));
+ }
+
+ /**
+ * Save the JSON leaves of the tree rooted as the node 'node' with subject key 'sk'.
+ *
+ * @param sk the string of the subject key.
+ * @param s the subject name.
+ * @param node the node rooting this subtree.
+ */
+ private void saveSubjectJson(String sk,
+ String s, ObjectNode node) {
+ node.fieldNames().forEachRemaining(c ->
+ this.jsons.put(new InnerConfigPosition(sk, s, c), node.path(c)));
+ }
+
+ /**
+ * Iterate through the JSON and populate a list of the leaf nodes of the structure.
+ */
+ private void populateConfigurations() {
+ root.fieldNames().forEachRemaining(sk ->
+ saveJson(sk, (ObjectNode) root.path(sk)));
+
+ }
+
+ /**
+ * Apply the configurations associated with all of the config classes that
+ * are imported and have not yet been applied.
+ */
+ private void applyConfigurations() {
+ Iterator<Map.Entry<InnerConfigPosition, JsonNode>> iter = jsons.entrySet().iterator();
+
+ Map.Entry<InnerConfigPosition, JsonNode> entry;
+ InnerConfigPosition key;
+ JsonNode node;
+ String subjectKey;
+ String subjectString;
+ String configKey;
+
+ while (iter.hasNext()) {
+ entry = iter.next();
+ node = entry.getValue();
+ key = entry.getKey();
+ subjectKey = key.subjectKey();
+ subjectString = key.subject();
+ configKey = key.configKey();
+
+ Class<? extends Config> configClass =
+ networkConfigService.getConfigClass(subjectKey, configKey);
+ //Check that the config class has been imported
+ if (configClass != null) {
+
+ Object subject = networkConfigService.getSubjectFactory(subjectKey).
+ createSubject(subjectString);
+
+ //Apply the configuration
+ networkConfigService.applyConfig(subject, configClass, node);
+
+ //Now that it has been applied the corresponding JSON entry is no longer needed
+ iter.remove();
+ }
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java
new file mode 100644
index 00000000..5cd96cab
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/NetworkConfigManager.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.config.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.NetworkConfigStore;
+import org.onosproject.net.config.NetworkConfigStoreDelegate;
+import org.onosproject.net.config.SubjectFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of the network configuration subsystem.
+ */
+@Component(immediate = true)
+@Service
+public class NetworkConfigManager
+ extends AbstractListenerManager<NetworkConfigEvent, NetworkConfigListener>
+ implements NetworkConfigRegistry, NetworkConfigService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String NULL_FACTORY_MSG = "Factory cannot be null";
+ private static final String NULL_SCLASS_MSG = "Subject class cannot be null";
+ private static final String NULL_CCLASS_MSG = "Config class cannot be null";
+ private static final String NULL_SUBJECT_MSG = "Subject cannot be null";
+
+ // Inventory of configuration factories
+ private final Map<ConfigKey, ConfigFactory> factories = Maps.newConcurrentMap();
+
+ // Secondary indices to retrieve subject and config classes by keys
+ private final Map<String, SubjectFactory> subjectClasses = Maps.newConcurrentMap();
+ private final Map<Class, SubjectFactory> subjectClassKeys = Maps.newConcurrentMap();
+ private final Map<ConfigIdentifier, Class<? extends Config>> configClasses = Maps.newConcurrentMap();
+
+ private final NetworkConfigStoreDelegate storeDelegate = new InternalStoreDelegate();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigStore store;
+
+
+ @Activate
+ public void activate() {
+ eventDispatcher.addSink(NetworkConfigEvent.class, listenerRegistry);
+ store.setDelegate(storeDelegate);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(NetworkConfigEvent.class);
+ store.unsetDelegate(storeDelegate);
+ log.info("Stopped");
+ }
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void registerConfigFactory(ConfigFactory configFactory) {
+ checkNotNull(configFactory, NULL_FACTORY_MSG);
+ factories.put(key(configFactory), configFactory);
+ configClasses.put(identifier(configFactory), configFactory.configClass());
+
+ SubjectFactory subjectFactory = configFactory.subjectFactory();
+ subjectClasses.putIfAbsent(subjectFactory.subjectKey(), subjectFactory);
+ subjectClassKeys.putIfAbsent(subjectFactory.subjectClass(), subjectFactory);
+
+ store.addConfigFactory(configFactory);
+ }
+
+ @Override
+ public void unregisterConfigFactory(ConfigFactory configFactory) {
+ checkNotNull(configFactory, NULL_FACTORY_MSG);
+ factories.remove(key(configFactory));
+ configClasses.remove(identifier(configFactory));
+
+ // Note that we are deliberately not removing subject factory key bindings.
+ store.removeConfigFactory(configFactory);
+ }
+
+ @Override
+ public Set<ConfigFactory> getConfigFactories() {
+ return ImmutableSet.copyOf(factories.values());
+ }
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <S, C extends Config<S>> Set<ConfigFactory<S, C>> getConfigFactories(Class<S> subjectClass) {
+ ImmutableSet.Builder<ConfigFactory<S, C>> builder = ImmutableSet.builder();
+ factories.forEach((key, factory) -> {
+ if (factory.subjectFactory().subjectClass().equals(subjectClass)) {
+ builder.add(factory);
+ }
+ });
+ return builder.build();
+ }
+
+ @Override
+ public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) {
+ checkNotNull(configClass, NULL_CCLASS_MSG);
+ return store.getConfigFactory(configClass);
+ }
+
+
+ @Override
+ public Set<Class> getSubjectClasses() {
+ ImmutableSet.Builder<Class> builder = ImmutableSet.builder();
+ factories.forEach((k, v) -> builder.add(k.subjectClass));
+ return builder.build();
+ }
+
+ @Override
+ public SubjectFactory getSubjectFactory(String subjectKey) {
+ return subjectClasses.get(subjectKey);
+ }
+
+ @Override
+ public SubjectFactory getSubjectFactory(Class subjectClass) {
+ return subjectClassKeys.get(subjectClass);
+ }
+
+ @Override
+ public Class<? extends Config> getConfigClass(String subjectKey, String configKey) {
+ return configClasses.get(new ConfigIdentifier(subjectKey, configKey));
+ }
+
+ @Override
+ public <S> Set<S> getSubjects(Class<S> subjectClass) {
+ checkNotNull(subjectClass, NULL_SCLASS_MSG);
+ return store.getSubjects(subjectClass);
+ }
+
+ @Override
+ public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
+ checkNotNull(subjectClass, NULL_SCLASS_MSG);
+ checkNotNull(configClass, NULL_CCLASS_MSG);
+ return store.getSubjects(subjectClass, configClass);
+ }
+
+ @Override
+ public <S> Set<Config<S>> getConfigs(S subject) {
+ checkNotNull(subject, NULL_SUBJECT_MSG);
+ Set<Class<? extends Config<S>>> configClasses = store.getConfigClasses(subject);
+ ImmutableSet.Builder<Config<S>> cfg = ImmutableSet.builder();
+ configClasses.forEach(cc -> cfg.add(store.getConfig(subject, cc)));
+ return cfg.build();
+ }
+
+ @Override
+ public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) {
+ checkNotNull(subject, NULL_SUBJECT_MSG);
+ checkNotNull(configClass, NULL_CCLASS_MSG);
+ return store.getConfig(subject, configClass);
+ }
+
+
+ @Override
+ public <S, C extends Config<S>> C addConfig(S subject, Class<C> configClass) {
+ checkNotNull(subject, NULL_SUBJECT_MSG);
+ checkNotNull(configClass, NULL_CCLASS_MSG);
+ return store.createConfig(subject, configClass);
+ }
+
+ @Override
+ public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
+ checkNotNull(subject, NULL_SUBJECT_MSG);
+ checkNotNull(configClass, NULL_CCLASS_MSG);
+ return store.applyConfig(subject, configClass, json);
+ }
+
+ @Override
+ public <S, C extends Config<S>> void removeConfig(S subject, Class<C> configClass) {
+ checkNotNull(subject, NULL_SUBJECT_MSG);
+ checkNotNull(configClass, NULL_CCLASS_MSG);
+ store.clearConfig(subject, configClass);
+ }
+
+ // Auxiliary store delegate to receive notification about changes in
+ // the network configuration store state - by the store itself.
+ private class InternalStoreDelegate implements NetworkConfigStoreDelegate {
+ @Override
+ public void notify(NetworkConfigEvent event) {
+ post(event);
+ }
+ }
+
+
+ // Produces a key for uniquely tracking a config factory.
+ private static ConfigKey key(ConfigFactory factory) {
+ return new ConfigKey(factory.subjectFactory().subjectClass(), factory.configClass());
+ }
+
+ // Auxiliary key to track config factories.
+ protected static final class ConfigKey {
+ final Class subjectClass;
+ final Class configClass;
+
+ protected ConfigKey(Class subjectClass, Class configClass) {
+ this.subjectClass = subjectClass;
+ this.configClass = configClass;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subjectClass, configClass);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ConfigKey) {
+ final ConfigKey other = (ConfigKey) obj;
+ return Objects.equals(this.subjectClass, other.subjectClass)
+ && Objects.equals(this.configClass, other.configClass);
+ }
+ return false;
+ }
+ }
+
+ private static ConfigIdentifier identifier(ConfigFactory factory) {
+ return new ConfigIdentifier(factory.subjectFactory().subjectKey(), factory.configKey());
+ }
+
+ static final class ConfigIdentifier {
+ final String subjectKey;
+ final String configKey;
+
+ protected ConfigIdentifier(String subjectKey, String configKey) {
+ this.subjectKey = subjectKey;
+ this.configKey = configKey;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subjectKey, configKey);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ConfigIdentifier) {
+ final ConfigIdentifier other = (ConfigIdentifier) obj;
+ return Objects.equals(this.subjectKey, other.subjectKey)
+ && Objects.equals(this.configKey, other.configKey);
+ }
+ return false;
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java
new file mode 100644
index 00000000..38f76941
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/config/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of the network configuration subsystem.
+ */
+package org.onosproject.net.config.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java
new file mode 100644
index 00000000..7900d185
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.device.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.net.config.ConfigOperator;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DeviceDescription;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+
+/**
+ * Implementations of merge policies for various sources of device configuration
+ * information. This includes applications, provides, and network configurations.
+ */
+public final class BasicDeviceOperator implements ConfigOperator {
+
+ protected static final double DEFAULT_COORD = -1.0;
+ private static final Logger log = getLogger(BasicDeviceOperator.class);
+
+ private BasicDeviceOperator() {
+ }
+
+ /**
+ * Generates a DeviceDescription containing fields from a DeviceDescription and
+ * a DeviceConfig.
+ *
+ * @param bdc the device config entity from network config
+ * @param descr a DeviceDescription
+ * @return DeviceDescription based on both sources
+ */
+ public static DeviceDescription combine(BasicDeviceConfig bdc, DeviceDescription descr) {
+ if (bdc == null) {
+ return descr;
+ }
+
+ Device.Type type = descr.type();
+ if (bdc.type() != null && bdc.type() != type) {
+ type = bdc.type();
+ }
+
+ SparseAnnotations sa = combine(bdc, descr.annotations());
+ return new DefaultDeviceDescription(descr.deviceURI(), type, descr.manufacturer(),
+ descr.hwVersion(), descr.swVersion(),
+ descr.serialNumber(), descr.chassisId(), sa);
+ }
+
+ /**
+ * Generates an annotation from an existing annotation and DeviceConfig.
+ *
+ * @param bdc the device config entity from network config
+ * @param an the annotation
+ * @return annotation combining both sources
+ */
+ public static SparseAnnotations combine(BasicDeviceConfig bdc, SparseAnnotations an) {
+ DefaultAnnotations.Builder newBuilder = DefaultAnnotations.builder();
+ if (!Objects.equals(bdc.driver(), an.value(AnnotationKeys.DRIVER))) {
+ newBuilder.set(AnnotationKeys.DRIVER, bdc.driver());
+ }
+ if (bdc.name() != null) {
+ newBuilder.set(AnnotationKeys.NAME, bdc.name());
+ }
+ if (bdc.latitude() != DEFAULT_COORD) {
+ newBuilder.set(AnnotationKeys.LATITUDE, Double.toString(bdc.latitude()));
+ }
+ if (bdc.longitude() != DEFAULT_COORD) {
+ newBuilder.set(AnnotationKeys.LONGITUDE, Double.toString(bdc.longitude()));
+ }
+ if (bdc.rackAddress() != null) {
+ newBuilder.set(AnnotationKeys.RACK_ADDRESS, bdc.rackAddress());
+ }
+ if (bdc.owner() != null) {
+ newBuilder.set(AnnotationKeys.OWNER, bdc.owner());
+ }
+ DefaultAnnotations newAnnotations = newBuilder.build();
+ return DefaultAnnotations.union(an, newAnnotations);
+ }
+
+ public static DeviceDescription descriptionOf(Device device) {
+ checkNotNull(device, "Must supply non-null Device");
+ return new DefaultDeviceDescription(device.id().uri(), device.type(),
+ device.manufacturer(), device.hwVersion(),
+ device.swVersion(), device.serialNumber(),
+ device.chassisId(), (SparseAnnotations) device.annotations());
+ }
+}
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
new file mode 100644
index 00000000..b0b3abe2
--- /dev/null
+++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
@@ -0,0 +1,765 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.device.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.provider.AbstractListenerProviderRegistry;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
+import org.onosproject.net.config.basics.OpticalPortConfig;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.Device.Type;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceProvider;
+import org.onosproject.net.device.DeviceProviderRegistry;
+import org.onosproject.net.device.DeviceProviderService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceStore;
+import org.onosproject.net.device.DeviceStoreDelegate;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.MastershipRole.*;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.security.AppPermission.Type.*;
+
+/**
+ * Provides implementation of the device SB &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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.&nbsp;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 &amp; 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 &amp; consistent topology graph views.
+ */
+package org.onosproject.net.topology.impl;
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
new file mode 100644
index 00000000..1ce31ac3
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.app.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationListener;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStoreAdapter;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.app.ApplicationState.ACTIVE;
+import static org.onosproject.app.ApplicationState.INSTALLED;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+
+/**
+ * Test of the application manager implementation.
+ */
+public class ApplicationManagerTest {
+
+ public static final DefaultApplicationId APP_ID = new DefaultApplicationId(1, APP_NAME);
+
+ private ApplicationManager mgr = new ApplicationManager();
+ private ApplicationListener listener = new TestListener();
+
+ @Before
+ public void setUp() {
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ mgr.featuresService = new TestFeaturesService();
+ mgr.store = new TestStore();
+ mgr.activate();
+ mgr.addListener(listener);
+ }
+
+ @After
+ public void tearDown() {
+ mgr.removeListener(listener);
+ mgr.deactivate();
+ }
+
+ private void validate(Application app) {
+ assertEquals("incorrect name", APP_NAME, app.id().name());
+ assertEquals("incorrect version", VER, app.version());
+ assertEquals("incorrect origin", ORIGIN, app.origin());
+
+ assertEquals("incorrect description", DESC, app.description());
+ assertEquals("incorrect features URI", FURL, app.featuresRepo().get());
+ assertEquals("incorrect features", FEATURES, app.features());
+ }
+
+ @Test
+ public void install() {
+ InputStream stream = ApplicationArchive.class.getResourceAsStream("app.zip");
+ Application app = mgr.install(stream);
+ validate(app);
+ assertEquals("incorrect features URI used", app.featuresRepo().get(),
+ ((TestFeaturesService) mgr.featuresService).uri);
+ assertEquals("incorrect app count", 1, mgr.getApplications().size());
+ assertEquals("incorrect app", app, mgr.getApplication(APP_ID));
+ assertEquals("incorrect app state", INSTALLED, mgr.getState(APP_ID));
+ }
+
+ @Test
+ public void uninstall() {
+ install();
+ mgr.uninstall(APP_ID);
+ assertEquals("incorrect app count", 0, mgr.getApplications().size());
+ }
+
+ @Test
+ public void activate() {
+ install();
+ mgr.activate(APP_ID);
+ assertEquals("incorrect app state", ACTIVE, mgr.getState(APP_ID));
+ }
+
+ @Test
+ public void deactivate() {
+ activate();
+ mgr.deactivate(APP_ID);
+ assertEquals("incorrect app state", INSTALLED, mgr.getState(APP_ID));
+ }
+
+
+ private class TestListener implements ApplicationListener {
+ private ApplicationEvent event;
+
+ @Override
+ public void event(ApplicationEvent event) {
+ this.event = event;
+ }
+ }
+
+ private class TestStore extends ApplicationStoreAdapter {
+
+ private Application app;
+ private ApplicationState state;
+
+ @Override
+ public Application create(InputStream appDescStream) {
+ app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, PERMS,
+ Optional.of(FURL), FEATURES);
+ state = INSTALLED;
+ delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
+ return app;
+ }
+
+ @Override
+ public Set<Application> getApplications() {
+ return app != null ? ImmutableSet.of(app) : ImmutableSet.of();
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ return app;
+ }
+
+ @Override
+ public void remove(ApplicationId appId) {
+ delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app));
+ app = null;
+ state = null;
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ return state;
+ }
+
+ @Override
+ public void activate(ApplicationId appId) {
+ state = ApplicationState.ACTIVE;
+ delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
+ }
+
+ @Override
+ public void deactivate(ApplicationId appId) {
+ state = INSTALLED;
+ delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
+ }
+ }
+
+ private class TestFeaturesService extends FeaturesServiceAdapter {
+ private URI uri;
+ private Set<String> features = new HashSet<>();
+
+ @Override
+ public void addRepository(URI uri) throws Exception {
+ this.uri = uri;
+ }
+
+ @Override
+ public void removeRepository(URI uri) throws Exception {
+ this.uri = null;
+ }
+
+ @Override
+ public void installFeature(String name) throws Exception {
+ features.add(name);
+ }
+
+ @Override
+ public void uninstallFeature(String name) throws Exception {
+ features.remove(name);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java
new file mode 100644
index 00000000..fc19b0a1
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.app.impl;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.Repository;
+
+import java.net.URI;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Adapter for testing against Apache Karaf feature service.
+ */
+public class FeaturesServiceAdapter implements org.apache.karaf.features.FeaturesService {
+ @Override
+ public void validateRepository(URI uri) throws Exception {
+
+ }
+
+ @Override
+ public void addRepository(URI uri) throws Exception {
+
+ }
+
+ @Override
+ public void addRepository(URI uri, boolean install) throws Exception {
+
+ }
+
+ @Override
+ public void removeRepository(URI uri) throws Exception {
+
+ }
+
+ @Override
+ public void removeRepository(URI uri, boolean uninstall) throws Exception {
+
+ }
+
+ @Override
+ public void restoreRepository(URI uri) throws Exception {
+
+ }
+
+ @Override
+ public Repository[] listRepositories() {
+ return new Repository[0];
+ }
+
+ @Override
+ public Repository getRepository(String repoName) {
+ return null;
+ }
+
+ @Override
+ public Repository getRepository(URI uri) {
+ return null;
+ }
+
+ @Override
+ public String getRepositoryName(URI uri) {
+ return null;
+ }
+
+ @Override
+ public void installFeature(String name) throws Exception {
+
+ }
+
+ @Override
+ public void installFeature(String name, EnumSet<Option> options) throws Exception {
+
+ }
+
+ @Override
+ public void installFeature(String name, String version) throws Exception {
+
+ }
+
+ @Override
+ public void installFeature(String name, String version, EnumSet<Option> options) throws Exception {
+
+ }
+
+ @Override
+ public void installFeature(Feature f, EnumSet<Option> options) throws Exception {
+
+ }
+
+ @Override
+ public void installFeatures(Set<Feature> features, EnumSet<Option> options) throws Exception {
+
+ }
+
+ @Override
+ public void uninstallFeature(String name, EnumSet<Option> options) throws Exception {
+
+ }
+
+ @Override
+ public void uninstallFeature(String name) throws Exception {
+
+ }
+
+ @Override
+ public void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception {
+
+ }
+
+ @Override
+ public void uninstallFeature(String name, String version) throws Exception {
+
+ }
+
+ @Override
+ public Feature[] listFeatures() throws Exception {
+ return new Feature[0];
+ }
+
+ @Override
+ public Feature[] listInstalledFeatures() {
+ return new Feature[0];
+ }
+
+ @Override
+ public boolean isInstalled(Feature f) {
+ return false;
+ }
+
+ @Override
+ public Feature[] getFeatures(String name, String version) throws Exception {
+ return new Feature[0];
+ }
+
+ @Override
+ public Feature[] getFeatures(String name) throws Exception {
+ return new Feature[0];
+ }
+
+ @Override
+ public Feature getFeature(String name, String version) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Feature getFeature(String name) throws Exception {
+ return null;
+ }
+
+ @Override
+ public void refreshRepository(URI uri) throws Exception {
+
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java
new file mode 100644
index 00000000..66e9c7b4
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/cfg/impl/ConfigPropertyDefinitionsTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cfg.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.onosproject.cfg.ConfigProperty;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onosproject.cfg.ConfigProperty.Type.STRING;
+import static org.onosproject.cfg.ConfigProperty.defineProperty;
+import static org.onosproject.cfg.impl.ConfigPropertyDefinitions.read;
+import static org.onosproject.cfg.impl.ConfigPropertyDefinitions.write;
+
+/**
+ * Tests of the config property definitions utility.
+ */
+public class ConfigPropertyDefinitionsTest {
+
+ @Test
+ public void basics() throws IOException {
+ Set<ConfigProperty> original = ImmutableSet
+ .of(defineProperty("foo", STRING, "dingo", "FOO"),
+ defineProperty("bar", STRING, "bat", "BAR"));
+ ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+ write(out, original);
+ Set<ConfigProperty> read = read(new ByteArrayInputStream(out.toByteArray()));
+ assertEquals("incorrect defs", original, read);
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java
new file mode 100644
index 00000000..bf1a1ff3
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/cluster/impl/MastershipManagerTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cluster.impl;
+
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipStore;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.trivial.SimpleMastershipStore;
+
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.onosproject.net.MastershipRole.MASTER;
+import static org.onosproject.net.MastershipRole.NONE;
+import static org.onosproject.net.MastershipRole.STANDBY;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+
+/**
+ * Test codifying the mastership service contracts.
+ */
+public class MastershipManagerTest {
+
+ private static final NodeId NID_LOCAL = new NodeId("local");
+ private static final NodeId NID_OTHER = new NodeId("foo");
+ private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
+ private static final DeviceId DEV_MASTER = DeviceId.deviceId("of:1");
+ private static final DeviceId DEV_OTHER = DeviceId.deviceId("of:2");
+
+ private MastershipManager mgr;
+ protected MastershipService service;
+
+ @Before
+ public void setUp() {
+ mgr = new MastershipManager();
+ service = mgr;
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ mgr.clusterService = new TestClusterService();
+ mgr.store = new TestSimpleMastershipStore(mgr.clusterService);
+ mgr.activate();
+ }
+
+ @After
+ public void tearDown() {
+ mgr.deactivate();
+ mgr.clusterService = null;
+ injectEventDispatcher(mgr, null);
+ mgr.store = null;
+ }
+
+ @Test
+ public void setRole() {
+ mgr.setRole(NID_OTHER, DEV_MASTER, MASTER);
+ assertEquals("wrong local role:", NONE, mgr.getLocalRole(DEV_MASTER));
+ assertEquals("wrong obtained role:", STANDBY, Futures.getUnchecked(mgr.requestRoleFor(DEV_MASTER)));
+
+ //set to master
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ assertEquals("wrong local role:", MASTER, mgr.getLocalRole(DEV_MASTER));
+ }
+
+ @Test
+ public void relinquishMastership() {
+ //no backups - should just turn to NONE for device.
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ assertEquals("wrong role:", MASTER, mgr.getLocalRole(DEV_MASTER));
+ mgr.relinquishMastership(DEV_MASTER);
+ assertNull("wrong master:", mgr.getMasterFor(DEV_OTHER));
+ assertEquals("wrong role:", NONE, mgr.getLocalRole(DEV_MASTER));
+
+ //not master, nothing should happen
+ mgr.setRole(NID_LOCAL, DEV_OTHER, NONE);
+ mgr.relinquishMastership(DEV_OTHER);
+ assertNull("wrong role:", mgr.getMasterFor(DEV_OTHER));
+
+ //provide NID_OTHER as backup and relinquish
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_MASTER));
+ mgr.setRole(NID_OTHER, DEV_MASTER, STANDBY);
+ mgr.relinquishMastership(DEV_MASTER);
+ assertEquals("wrong master:", NID_OTHER, mgr.getMasterFor(DEV_MASTER));
+ }
+
+ @Test
+ public void requestRoleFor() {
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ mgr.setRole(NID_OTHER, DEV_OTHER, MASTER);
+
+ //local should be master for one but standby for other
+ assertEquals("wrong role:", MASTER, Futures.getUnchecked(mgr.requestRoleFor(DEV_MASTER)));
+ assertEquals("wrong role:", STANDBY, Futures.getUnchecked(mgr.requestRoleFor(DEV_OTHER)));
+ }
+
+ @Test
+ public void getMasterFor() {
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ mgr.setRole(NID_OTHER, DEV_OTHER, MASTER);
+ assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_MASTER));
+ assertEquals("wrong master:", NID_OTHER, mgr.getMasterFor(DEV_OTHER));
+
+ //have NID_OTHER hand over DEV_OTHER to NID_LOCAL
+ mgr.setRole(NID_LOCAL, DEV_OTHER, MASTER);
+ assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_OTHER));
+ }
+
+ @Test
+ public void getDevicesOf() {
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ mgr.setRole(NID_LOCAL, DEV_OTHER, STANDBY);
+ assertEquals("should be one device:", 1, mgr.getDevicesOf(NID_LOCAL).size());
+ //hand both devices to NID_LOCAL
+ mgr.setRole(NID_LOCAL, DEV_OTHER, MASTER);
+ assertEquals("should be two devices:", 2, mgr.getDevicesOf(NID_LOCAL).size());
+ }
+
+ @Test
+ public void termService() {
+ MastershipTermService ts = mgr;
+
+ //term = 1 for both
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ assertEquals("inconsistent term: ", 1, ts.getMastershipTerm(DEV_MASTER).termNumber());
+
+ //hand devices to NID_LOCAL and back: term = 1 + 2
+ mgr.setRole(NID_OTHER, DEV_MASTER, MASTER);
+ mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+ assertEquals("inconsistent terms: ", 3, ts.getMastershipTerm(DEV_MASTER).termNumber());
+ }
+
+ private final class TestClusterService extends ClusterServiceAdapter {
+
+ ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST);
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return local;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return Sets.newHashSet();
+ }
+
+ }
+
+ private final class TestSimpleMastershipStore extends SimpleMastershipStore
+ implements MastershipStore {
+
+ public TestSimpleMastershipStore(ClusterService clusterService) {
+ super.clusterService = clusterService;
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java
new file mode 100644
index 00000000..383e981a
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/DummyIdBlockAllocator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.onosproject.core.IdBlock;
+
+public class DummyIdBlockAllocator implements IdBlockAllocator {
+ private long blockTop;
+ private static final long BLOCK_SIZE = 0x1000000L;
+
+ /**
+ * Returns a block of IDs which are unique and unused.
+ * Range of IDs is fixed size and is assigned incrementally as this method
+ * called.
+ *
+ * @return an IdBlock containing a set of unique IDs
+ */
+ @Override
+ public IdBlock allocateUniqueIdBlock() {
+ synchronized (this) {
+ long blockHead = blockTop;
+ long blockTail = blockTop + BLOCK_SIZE;
+
+ IdBlock block = new IdBlock(blockHead, BLOCK_SIZE);
+ blockTop = blockTail;
+
+ return block;
+ }
+ }
+
+ @Override
+ public IdBlock allocateUniqueIdBlock(long range) {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java
new file mode 100644
index 00000000..8a4e6185
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/IdBlockAllocatorBasedIdGeneratorTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.easymock.EasyMock;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.IdBlock;
+
+/**
+ * Suites of test of {@link org.onosproject.core.impl.BlockAllocatorBasedIdGenerator}.
+ */
+public class IdBlockAllocatorBasedIdGeneratorTest {
+ private IdBlockAllocator allocator;
+ private BlockAllocatorBasedIdGenerator sut;
+
+ @Before
+ public void setUp() {
+ allocator = EasyMock.createMock(IdBlockAllocator.class);
+
+ }
+
+ /**
+ * Tests generated IntentId sequences using two {@link org.onosproject.core.IdBlock blocks}.
+ */
+ @Test
+ public void testIds() {
+ EasyMock.expect(allocator.allocateUniqueIdBlock())
+ .andReturn(new IdBlock(0, 3))
+ .andReturn(new IdBlock(4, 3));
+
+ EasyMock.replay(allocator);
+ sut = new BlockAllocatorBasedIdGenerator(allocator);
+
+ Assert.assertThat(sut.getNewId(), Matchers.is(0L));
+ Assert.assertThat(sut.getNewId(), Matchers.is(1L));
+ Assert.assertThat(sut.getNewId(), Matchers.is(2L));
+
+ Assert.assertThat(sut.getNewId(), Matchers.is(4L));
+ Assert.assertThat(sut.getNewId(), Matchers.is(5L));
+ Assert.assertThat(sut.getNewId(), Matchers.is(6L));
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java
new file mode 100644
index 00000000..474d1705
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/core/impl/TestCoreManager.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.core.impl;
+
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+
+public class TestCoreManager extends CoreServiceAdapter {
+
+ @Override
+ public IdGenerator getIdGenerator(String topic) {
+ IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
+ return new BlockAllocatorBasedIdGenerator(idBlockAllocator);
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java
new file mode 100644
index 00000000..9ba3db59
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/event/impl/CoreEventDispatcherTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.event.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.event.EventSink;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the even dispatcher mechanism.
+ */
+public class CoreEventDispatcherTest {
+
+ private final CoreEventDispatcher dispatcher = new CoreEventDispatcher();
+ private final PrickleSink prickleSink = new PrickleSink();
+ private final GooSink gooSink = new GooSink();
+
+ @Before
+ public void setUp() {
+ dispatcher.activate();
+ dispatcher.addSink(Prickle.class, prickleSink);
+ dispatcher.addSink(Goo.class, gooSink);
+ }
+
+ @After
+ public void tearDown() {
+ dispatcher.removeSink(Goo.class);
+ dispatcher.removeSink(Prickle.class);
+ dispatcher.deactivate();
+ }
+
+ @Test
+ public void post() throws Exception {
+ prickleSink.latch = new CountDownLatch(1);
+ dispatcher.post(new Prickle("yo"));
+ prickleSink.latch.await(100, TimeUnit.MILLISECONDS);
+ validate(prickleSink, "yo");
+ validate(gooSink);
+ }
+
+ @Test
+ public void postEventWithBadSink() throws Exception {
+ gooSink.latch = new CountDownLatch(1);
+ dispatcher.post(new Goo("boom"));
+ gooSink.latch.await(100, TimeUnit.MILLISECONDS);
+ validate(gooSink, "boom");
+ validate(prickleSink);
+ }
+
+ @Test
+ public void postEventWithNoSink() throws Exception {
+ dispatcher.post(new Thing("boom"));
+ validate(gooSink);
+ validate(prickleSink);
+ }
+
+ private void validate(Sink sink, String... strings) {
+ int i = 0;
+ assertEquals("incorrect event count", strings.length, sink.subjects.size());
+ for (String string : strings) {
+ assertEquals("incorrect event", string, sink.subjects.get(i++));
+ }
+ }
+
+ private enum Type { FOO }
+
+ private static class Thing extends AbstractEvent<Type, String> {
+ protected Thing(String subject) {
+ super(Type.FOO, subject);
+ }
+ }
+
+ private static class Prickle extends Thing {
+ protected Prickle(String subject) {
+ super(subject);
+ }
+ }
+
+ private static class Goo extends Thing {
+ protected Goo(String subject) {
+ super(subject);
+ }
+ }
+
+ private static class Sink {
+ final List<String> subjects = new ArrayList<>();
+ CountDownLatch latch;
+
+ protected void process(String subject) {
+ subjects.add(subject);
+ latch.countDown();
+ }
+ }
+
+ private static class PrickleSink extends Sink implements EventSink<Prickle> {
+ @Override
+ public void process(Prickle event) {
+ process(event.subject());
+ }
+ }
+
+ private static class GooSink extends Sink implements EventSink<Goo> {
+ @Override
+ public void process(Goo event) {
+ process(event.subject());
+ throw new IllegalStateException("BOOM!");
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java
new file mode 100644
index 00000000..2d03bfc8
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/config/impl/NetworkConfigManagerTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.config.impl;
+
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onosproject.event.EventDeliveryServiceAdapter;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.SubjectFactory;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.store.config.impl.DistributedNetworkConfigStore;
+import org.onosproject.store.service.TestStorageService;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Unit tests for network config registry.
+ */
+public class NetworkConfigManagerTest {
+ private NetworkConfigManager manager;
+ private NetworkConfigRegistry registry;
+ private NetworkConfigService configService;
+ private DistributedNetworkConfigStore configStore;
+
+ /**
+ * Config classes for testing.
+ */
+ public class BasicConfig1 extends Config<String> { }
+ public class BasicConfig2 extends Config<String> { }
+
+ public class MockSubjectFactory extends SubjectFactory<String> {
+ protected MockSubjectFactory(Class<String> subjectClass, String subjectKey) {
+ super(subjectClass, subjectKey);
+ }
+
+ @Override
+ public String createSubject(String subjectKey) {
+ return subjectKey + "-subject";
+ }
+ }
+
+ /**
+ * Config factory classes for testing.
+ */
+ public class MockConfigFactory1 extends ConfigFactory<String, BasicConfig1> {
+ protected MockConfigFactory1(SubjectFactory<String> subjectFactory,
+ Class<BasicConfig1> configClass, String configKey) {
+ super(subjectFactory, configClass, configKey);
+ }
+ @Override
+ public BasicConfig1 createConfig() {
+ return new BasicConfig1();
+ }
+ }
+
+ public class MockConfigFactory2 extends ConfigFactory<String, BasicConfig2> {
+ protected MockConfigFactory2(SubjectFactory<String> subjectFactory,
+ Class<BasicConfig2> configClass, String configKey) {
+ super(subjectFactory, configClass, configKey);
+ }
+ @Override
+ public BasicConfig2 createConfig() {
+ return new BasicConfig2();
+ }
+ }
+
+ MockSubjectFactory factory1 = new MockSubjectFactory(String.class,
+ "key1");
+ MockSubjectFactory factory2 = new MockSubjectFactory(String.class,
+ "key2");
+
+ MockConfigFactory1 config1Factory = new MockConfigFactory1(factory1,
+ BasicConfig1.class, "config1");
+ MockConfigFactory2 config2Factory = new MockConfigFactory2(factory2,
+ BasicConfig2.class, "config2");
+
+
+ @Before
+ public void setUp() throws Exception {
+ configStore = new DistributedNetworkConfigStore();
+ TestUtils.setField(configStore, "storageService", new TestStorageService());
+ configStore.activate();
+ manager = new NetworkConfigManager();
+ manager.store = configStore;
+ NetTestTools.injectEventDispatcher(manager, new EventDeliveryServiceAdapter());
+ manager.activate();
+ registry = manager;
+ configService = manager;
+ }
+
+ @After
+ public void tearDown() {
+ configStore.deactivate();
+ manager.deactivate();
+ }
+
+ @Test
+ public void testRegistry() {
+ assertThat(registry.getConfigFactories(), hasSize(0));
+ assertThat(registry.getConfigFactories(String.class), hasSize(0));
+ assertThat(registry.getConfigFactory(BasicConfig1.class), nullValue());
+
+ registry.registerConfigFactory(config1Factory);
+ registry.registerConfigFactory(config2Factory);
+
+ assertThat(registry.getConfigFactories(), hasSize(2));
+ assertThat(registry.getConfigFactories(String.class), hasSize(2));
+
+ ConfigFactory queried = registry.getConfigFactory(BasicConfig1.class);
+ assertThat(queried, is(config1Factory));
+
+ registry.unregisterConfigFactory(queried);
+ // Factory associations are not removed according to code documentation
+ assertThat(registry.getConfigFactories(), hasSize(1));
+ assertThat(registry.getConfigFactories(String.class), hasSize(1));
+ assertThat(registry.getConfigFactory(BasicConfig1.class), nullValue());
+ }
+
+ @Test
+ public void configIdEquals() {
+ NetworkConfigManager.ConfigIdentifier id1 =
+ new NetworkConfigManager.ConfigIdentifier("s1", "c1");
+ NetworkConfigManager.ConfigIdentifier likeId1 =
+ new NetworkConfigManager.ConfigIdentifier("s1", "c1");
+ NetworkConfigManager.ConfigIdentifier id2 =
+ new NetworkConfigManager.ConfigIdentifier("s1", "c2");
+ NetworkConfigManager.ConfigIdentifier id3 =
+ new NetworkConfigManager.ConfigIdentifier("s2", "c1");
+
+ new EqualsTester().addEqualityGroup(id1, likeId1)
+ .addEqualityGroup(id2)
+ .addEqualityGroup(id3)
+ .testEquals();
+ }
+
+ @Test
+ public void configKeyEquals() {
+ NetworkConfigManager.ConfigKey key1 =
+ new NetworkConfigManager.ConfigKey(String.class, String.class);
+ NetworkConfigManager.ConfigKey likeKey1 =
+ new NetworkConfigManager.ConfigKey(String.class, String.class);
+ NetworkConfigManager.ConfigKey key2 =
+ new NetworkConfigManager.ConfigKey(String.class, Integer.class);
+ NetworkConfigManager.ConfigKey key3 =
+ new NetworkConfigManager.ConfigKey(Integer.class, String.class);
+
+ new EqualsTester().addEqualityGroup(key1, likeKey1)
+ .addEqualityGroup(key2)
+ .addEqualityGroup(key3)
+ .testEquals();
+ }
+
+ /**
+ * Tests creation, query and removal of a factory.
+ */
+ @Test
+ public void testAddConfig() {
+
+ assertThat(configService.getSubjectFactory(String.class), nullValue());
+ assertThat(configService.getSubjectFactory("key"), nullValue());
+
+ registry.registerConfigFactory(config1Factory);
+ registry.registerConfigFactory(config2Factory);
+ configService.addConfig("configKey", BasicConfig1.class);
+
+ Config newConfig = configService.getConfig("configKey", BasicConfig1.class);
+ assertThat(newConfig, notNullValue());
+
+ assertThat(configService.getSubjectFactory(String.class), notNullValue());
+ assertThat(configService.getSubjectFactory("key1"), notNullValue());
+
+ Set<Class> classes = configService.getSubjectClasses();
+ assertThat(classes, hasSize(1));
+
+ Set<String> subjectsForClass =
+ configService.getSubjects(String.class);
+ assertThat(subjectsForClass, hasSize(1));
+
+ Set<String> subjectsForConfig =
+ configService.getSubjects(String.class, BasicConfig1.class);
+ assertThat(subjectsForConfig, hasSize(1));
+
+ Class queriedConfigClass = configService.getConfigClass("key1", "config1");
+ assertThat(queriedConfigClass == BasicConfig1.class, is(true));
+
+ Set<? extends Config> configs = configService.getConfigs("configKey");
+ assertThat(configs.size(), is(1));
+ configs.forEach(c -> assertThat(c, instanceOf(BasicConfig1.class)));
+
+ configService.removeConfig("configKey", BasicConfig1.class);
+ Config newConfigAfterRemove = configService.getConfig("configKey", BasicConfig1.class);
+ assertThat(newConfigAfterRemove, nullValue());
+ }
+
+ /**
+ * Tests creation, query and removal of a factory.
+ */
+ @Test
+ public void testApplyConfig() {
+
+ assertThat(configService.getSubjectFactory(String.class), nullValue());
+ assertThat(configService.getSubjectFactory("key"), nullValue());
+
+ registry.registerConfigFactory(config1Factory);
+ registry.registerConfigFactory(config2Factory);
+ configService.applyConfig("configKey", BasicConfig1.class, new ObjectMapper().createObjectNode());
+
+ Config newConfig = configService.getConfig("configKey", BasicConfig1.class);
+ assertThat(newConfig, notNullValue());
+
+ assertThat(configService.getSubjectFactory(String.class), notNullValue());
+ assertThat(configService.getSubjectFactory("key1"), notNullValue());
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java
new file mode 100644
index 00000000..8827c558
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/BasicDeviceOperatorTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.device.impl;
+
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.Device.Type.ROADM;
+import static org.junit.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DeviceDescription;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+public class BasicDeviceOperatorTest {
+
+ private static final String NAME1 = "of:foo";
+ private static final String NAME2 = "of:bar";
+ private static final String OWNER = "somebody";
+ private static final URI DURI = URI.create(NAME1);
+ private static final String MFR = "whitebox";
+ private static final String HW = "1.1.x";
+ private static final String SW = "3.9.1";
+ private static final String SN = "43311-12345";
+ private static final ChassisId CID = new ChassisId();
+
+ private static final SparseAnnotations SA = DefaultAnnotations.builder()
+ .set(AnnotationKeys.DRIVER, NAME2).build();
+
+ private static final DeviceDescription DEV1 = new DefaultDeviceDescription(
+ DURI, SWITCH, MFR, HW, SW, SN, CID, SA);
+
+ private final ConfigApplyDelegate delegate = new ConfigApplyDelegate() {
+ @Override
+ public void onApply(Config config) {
+ }
+ };
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private static final BasicDeviceConfig SW_BDC = new BasicDeviceConfig();
+ private static final BasicDeviceConfig RD_BDC = new BasicDeviceConfig();
+
+ @Before
+ public void setUp() {
+ SW_BDC.init(DeviceId.deviceId(NAME1), NAME1, JsonNodeFactory.instance.objectNode(), mapper, delegate);
+ SW_BDC.type(SWITCH).driver(NAME1).owner(OWNER);
+ RD_BDC.init(DeviceId.deviceId(NAME2), NAME2, JsonNodeFactory.instance.objectNode(), mapper, delegate);
+ RD_BDC.type(ROADM);
+ }
+
+ @Test
+ public void testDescOps() {
+ DeviceDescription desc = BasicDeviceOperator.combine(null, DEV1);
+ assertEquals(desc, DEV1);
+
+ // override driver name
+ desc = BasicDeviceOperator.combine(SW_BDC, DEV1);
+ assertEquals(NAME1, desc.annotations().value(AnnotationKeys.DRIVER));
+
+ // override Device Type
+ desc = BasicDeviceOperator.combine(RD_BDC, DEV1);
+ assertEquals(ROADM, desc.type());
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java
new file mode 100644
index 00000000..04f266f0
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.device.impl;
+
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.event.Event;
+import org.onosproject.net.config.NetworkConfigServiceAdapter;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceProvider;
+import org.onosproject.net.device.DeviceProviderRegistry;
+import org.onosproject.net.device.DeviceProviderService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.trivial.SimpleDeviceStore;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+
+/**
+ * Test codifying the device service & device provider service contracts.
+ */
+public class DeviceManagerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+ private static final String MFR = "whitebox";
+ private static final String HW = "1.1.x";
+ private static final String SW1 = "3.8.1";
+ private static final String SW2 = "3.9.5";
+ private static final String SN = "43311-12345";
+ private static final ChassisId CID = new ChassisId();
+
+ private static final PortNumber P1 = PortNumber.portNumber(1);
+ private static final PortNumber P2 = PortNumber.portNumber(2);
+ private static final PortNumber P3 = PortNumber.portNumber(3);
+ private static final NodeId NID_LOCAL = new NodeId("local");
+ private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
+
+ private DeviceManager mgr;
+
+ protected DeviceService service;
+ protected DeviceAdminService admin;
+ protected DeviceProviderRegistry registry;
+ protected DeviceProviderService providerService;
+ protected TestProvider provider;
+ protected TestListener listener = new TestListener();
+
+ @Before
+ public void setUp() {
+ mgr = new DeviceManager();
+ service = mgr;
+ admin = mgr;
+ registry = mgr;
+ mgr.store = new SimpleDeviceStore();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ TestMastershipManager mastershipManager = new TestMastershipManager();
+ mgr.mastershipService = mastershipManager;
+ mgr.termService = mastershipManager;
+ mgr.clusterService = new TestClusterService();
+ mgr.networkConfigService = new TestNetworkConfigService();
+ mgr.activate();
+
+
+ service.addListener(listener);
+
+ provider = new TestProvider();
+ providerService = registry.register(provider);
+ assertTrue("provider should be registered",
+ registry.getProviders().contains(provider.id()));
+ }
+
+ @After
+ public void tearDown() {
+ registry.unregister(provider);
+ assertFalse("provider should not be registered",
+ registry.getProviders().contains(provider.id()));
+ service.removeListener(listener);
+ mgr.deactivate();
+ }
+
+ private void connectDevice(DeviceId deviceId, String swVersion) {
+ DeviceDescription description =
+ new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+ HW, swVersion, SN, CID);
+ providerService.deviceConnected(deviceId, description);
+ assertNotNull("device should be found", service.getDevice(DID1));
+ }
+
+ @Test
+ public void deviceConnected() {
+ assertNull("device should not be found", service.getDevice(DID1));
+ connectDevice(DID1, SW1);
+ validateEvents(DEVICE_ADDED);
+
+ Iterator<Device> it = service.getDevices().iterator();
+ assertNotNull("one device expected", it.next());
+ assertFalse("only one device expected", it.hasNext());
+ assertEquals("incorrect device count", 1, service.getDeviceCount());
+ assertTrue("device should be available", service.isAvailable(DID1));
+ }
+
+ @Test
+ public void deviceDisconnected() {
+ connectDevice(DID1, SW1);
+ connectDevice(DID2, SW1);
+ validateEvents(DEVICE_ADDED, DEVICE_ADDED);
+ assertTrue("device should be available", service.isAvailable(DID1));
+
+ // Disconnect
+ providerService.deviceDisconnected(DID1);
+ assertNotNull("device should not be found", service.getDevice(DID1));
+ assertFalse("device should not be available", service.isAvailable(DID1));
+ validateEvents(DEVICE_AVAILABILITY_CHANGED);
+
+ // Reconnect
+ connectDevice(DID1, SW1);
+ validateEvents(DEVICE_AVAILABILITY_CHANGED);
+
+ assertEquals("incorrect device count", 2, service.getDeviceCount());
+ }
+
+ @Test
+ public void deviceUpdated() {
+ connectDevice(DID1, SW1);
+ validateEvents(DEVICE_ADDED);
+
+ connectDevice(DID1, SW2);
+ validateEvents(DEVICE_UPDATED);
+ }
+
+ @Test
+ public void getRole() {
+ connectDevice(DID1, SW1);
+ assertEquals("incorrect role", MastershipRole.MASTER, service.getRole(DID1));
+ }
+
+ @Test
+ public void updatePorts() {
+ connectDevice(DID1, SW1);
+ List<PortDescription> pds = new ArrayList<>();
+ pds.add(new DefaultPortDescription(P1, true));
+ pds.add(new DefaultPortDescription(P2, true));
+ pds.add(new DefaultPortDescription(P3, true));
+ providerService.updatePorts(DID1, pds);
+ validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED, PORT_ADDED);
+ pds.clear();
+
+ pds.add(new DefaultPortDescription(P1, false));
+ pds.add(new DefaultPortDescription(P3, true));
+ providerService.updatePorts(DID1, pds);
+ validateEvents(PORT_UPDATED, PORT_REMOVED);
+ }
+
+ @Test
+ public void updatePortStatus() {
+ connectDevice(DID1, SW1);
+ List<PortDescription> pds = new ArrayList<>();
+ pds.add(new DefaultPortDescription(P1, true));
+ pds.add(new DefaultPortDescription(P2, true));
+ providerService.updatePorts(DID1, pds);
+ validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED);
+
+ providerService.portStatusChanged(DID1, new DefaultPortDescription(P1, false));
+ validateEvents(PORT_UPDATED);
+ providerService.portStatusChanged(DID1, new DefaultPortDescription(P1, false));
+ assertTrue("no events expected", listener.events.isEmpty());
+ }
+
+ @Test
+ public void getPorts() {
+ connectDevice(DID1, SW1);
+ List<PortDescription> pds = new ArrayList<>();
+ pds.add(new DefaultPortDescription(P1, true));
+ pds.add(new DefaultPortDescription(P2, true));
+ providerService.updatePorts(DID1, pds);
+ validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED);
+ assertEquals("wrong port count", 2, service.getPorts(DID1).size());
+
+ Port port = service.getPort(DID1, P1);
+ assertEquals("incorrect port", P1, service.getPort(DID1, P1).number());
+ assertEquals("incorrect state", true, service.getPort(DID1, P1).isEnabled());
+ }
+
+ @Test
+ public void removeDevice() {
+ connectDevice(DID1, SW1);
+ connectDevice(DID2, SW2);
+ assertEquals("incorrect device count", 2, service.getDeviceCount());
+ admin.removeDevice(DID1);
+ assertNull("device should not be found", service.getDevice(DID1));
+ assertNotNull("device should be found", service.getDevice(DID2));
+ assertEquals("incorrect device count", 1, service.getDeviceCount());
+
+ }
+
+ protected void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("wrong events received", types.length, listener.events.size());
+ for (Event event : listener.events) {
+ assertEquals("incorrect event type", types[i], event.type());
+ i++;
+ }
+ listener.events.clear();
+ }
+
+
+ private class TestProvider extends AbstractProvider implements DeviceProvider {
+ private DeviceId deviceReceived;
+ private MastershipRole roleReceived;
+
+ public TestProvider() {
+ super(PID);
+ }
+
+ @Override
+ public void triggerProbe(DeviceId deviceId) {
+ }
+
+ @Override
+ public void roleChanged(DeviceId device, MastershipRole newRole) {
+ deviceReceived = device;
+ roleReceived = newRole;
+ }
+
+ @Override
+ public boolean isReachable(DeviceId device) {
+ return false;
+ }
+ }
+
+ private static class TestListener implements DeviceListener {
+ final List<DeviceEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(DeviceEvent event) {
+ events.add(event);
+ }
+ }
+
+ private static class TestMastershipManager
+ extends MastershipServiceAdapter implements MastershipTermService {
+ @Override
+ public MastershipRole getLocalRole(DeviceId deviceId) {
+ return MastershipRole.MASTER;
+ }
+
+ @Override
+ public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+ return Sets.newHashSet(DID1, DID2);
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ }
+
+ @Override
+ public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public MastershipTerm getMastershipTerm(DeviceId deviceId) {
+ // FIXME: just returning something not null
+ return MastershipTerm.of(NID_LOCAL, 1);
+ }
+ }
+
+ // code clone
+ private final class TestClusterService extends ClusterServiceAdapter {
+
+ ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST);
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return local;
+ }
+
+ }
+
+ private class TestNetworkConfigService extends NetworkConfigServiceAdapter {
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java
new file mode 100644
index 00000000..78bc08e0
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java
@@ -0,0 +1,80 @@
+package org.onosproject.net.device.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.config.basics.OpticalPortConfig;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OduCltPort;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.OduCltPortDescription;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+import static org.junit.Assert.assertEquals;
+
+public class OpticalPortOperatorTest {
+ private static final DeviceId DID = DeviceId.deviceId("op-test");
+ private static final String TPNAME = "test-port-100";
+ private static final String SPNAME = "out-port-200";
+ private static final String CFGNAME = "cfg-name";
+
+ private static final PortNumber NAMED = PortNumber.portNumber(100, TPNAME);
+ private static final PortNumber UNNAMED = PortNumber.portNumber(101);
+ private static final ConnectPoint NCP = new ConnectPoint(DID, UNNAMED);
+
+ private static final SparseAnnotations SA = DefaultAnnotations.builder()
+ .set(AnnotationKeys.STATIC_PORT, SPNAME)
+ .build();
+
+ private static final OduCltPortDescription N_DESC = new OduCltPortDescription(
+ NAMED, true, OduCltPort.SignalType.CLT_100GBE, SA);
+ private static final OduCltPortDescription FAULTY = new OduCltPortDescription(
+ null, true, OduCltPort.SignalType.CLT_100GBE);
+
+ private final ConfigApplyDelegate delegate = new MockCfgDelegate();
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private static final OpticalPortConfig N_OPC = new OpticalPortConfig();
+ private static final OpticalPortConfig UNN_OPC = new OpticalPortConfig();
+
+ @Before
+ public void setUp() {
+ N_OPC.init(NCP, TPNAME, JsonNodeFactory.instance.objectNode(), mapper, delegate);
+ UNN_OPC.init(NCP, TPNAME, JsonNodeFactory.instance.objectNode(), mapper, delegate);
+
+ N_OPC.portName(CFGNAME).portNumberName(101L).portType(Port.Type.ODUCLT).staticLambda(300L);
+ UNN_OPC.portType(Port.Type.ODUCLT);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testDescOps() {
+ // port-null desc + opc with port number name
+ OduCltPortDescription res = (OduCltPortDescription) OpticalPortOperator.combine(N_OPC, FAULTY);
+ assertEquals(CFGNAME, res.portNumber().name());
+ // full desc + opc with name
+ assertEquals(TPNAME, N_DESC.portNumber().name());
+ res = (OduCltPortDescription) OpticalPortOperator.combine(N_OPC, N_DESC);
+ long sl = Long.valueOf(res.annotations().value(AnnotationKeys.STATIC_LAMBDA));
+ assertEquals(CFGNAME, res.portNumber().name());
+ assertEquals(300L, sl);
+ // port-null desc + opc without port number name - throws RE
+ res = (OduCltPortDescription) OpticalPortOperator.combine(UNN_OPC, FAULTY);
+ }
+
+ private class MockCfgDelegate implements ConfigApplyDelegate {
+
+ @Override
+ public void onApply(@SuppressWarnings("rawtypes") Config config) {
+ config.apply();
+ }
+
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java
new file mode 100644
index 00000000..319412fe
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/edgeservice/impl/EdgeManagerTest.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.edgeservice.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.event.Event;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.edge.EdgePortEvent;
+import org.onosproject.net.edge.EdgePortListener;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyListener;
+import org.onosproject.net.topology.TopologyServiceAdapter;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.onosproject.net.edge.EdgePortEvent.Type.EDGE_PORT_ADDED;
+import static org.onosproject.net.edge.EdgePortEvent.Type.EDGE_PORT_REMOVED;
+import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
+import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
+import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED;
+
+/**
+ * Test of the edge port manager. Each device has ports '0' through 'numPorts - 1'
+ * as specified by the variable 'numPorts'.
+ */
+public class EdgeManagerTest {
+
+ private EdgeManager mgr;
+ private int totalPorts = 10;
+ private boolean alwaysReturnPorts = false;
+ private final Set<ConnectPoint> infrastructurePorts = Sets.newConcurrentHashSet();
+ private List<EdgePortEvent> events = Lists.newArrayList();
+ private final Map<DeviceId, Device> devices = Maps.newConcurrentMap();
+ private Set<OutboundPacket> packets = Sets.newConcurrentHashSet();
+ private final EdgePortListener testListener = new TestListener(events);
+ private TestTopologyManager testTopologyManager;
+
+ @Before
+ public void setUp() {
+ mgr = new EdgeManager();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ testTopologyManager = new TestTopologyManager(infrastructurePorts);
+ mgr.topologyService = testTopologyManager;
+ mgr.deviceService = new TestDeviceManager(devices);
+ mgr.packetService = new TestPacketManager();
+ mgr.activate();
+ mgr.addListener(testListener);
+
+ }
+
+
+ @After
+ public void tearDown() {
+ mgr.removeListener(testListener);
+ mgr.deactivate();
+ }
+
+ @Test
+ public void testBasics() {
+ //Setup
+ int numDevices = 20;
+ int numPorts = 4;
+ defaultPopulator(numDevices, numPorts);
+
+ assertEquals("Unexpected number of ports", numDevices * numPorts, infrastructurePorts.size());
+
+ assertFalse("no ports expected", mgr.getEdgePoints().iterator().hasNext());
+
+ assertFalse("Expected isEdge to return false",
+ mgr.isEdgePoint(NetTestTools.connectPoint(Integer.toString(1), 1)));
+
+ removeInfraPort(NetTestTools.connectPoint(Integer.toString(1), 1));
+ assertTrue("Expected isEdge to return false",
+ mgr.isEdgePoint(NetTestTools.connectPoint(Integer.toString(1), 1)));
+ }
+
+ @Test
+ public void testLinkUpdates() {
+ //Setup
+ ConnectPoint testPoint, referencePoint;
+
+ //Testing link removal
+ List<Event> eventsToAdd = Lists.newArrayList();
+ eventsToAdd.add(new LinkEvent(LINK_REMOVED, NetTestTools.link("a", 1, "b", 2)));
+ TopologyEvent event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ assertTrue("The list contained an unexpected number of events", events.size() == 2);
+ assertTrue("The first element is of the wrong type.",
+ events.get(0).type() == EDGE_PORT_ADDED);
+ assertTrue("The second element is of the wrong type.",
+ events.get(1).type() == EDGE_PORT_ADDED);
+
+ testPoint = events.get(0).subject();
+ referencePoint = NetTestTools.connectPoint("a", 1);
+ assertTrue("The port numbers of the first element are incorrect",
+ testPoint.port().toLong() == referencePoint.port().toLong());
+ assertTrue("The device id of the first element is incorrect.",
+ testPoint.deviceId().equals(referencePoint.deviceId()));
+
+ testPoint = events.get(1).subject();
+ referencePoint = NetTestTools.connectPoint("b", 2);
+ assertTrue("The port numbers of the second element are incorrect",
+ testPoint.port().toLong() == referencePoint.port().toLong());
+ assertTrue("The device id of the second element is incorrect.",
+ testPoint.deviceId().equals(referencePoint.deviceId()));
+
+ //Rebroadcast event to ensure it results in no additional events
+ testTopologyManager.listener.event(event);
+ assertTrue("The list contained an unexpected number of events", events.size() == 2);
+
+ //Testing link adding when links to remove exist
+ eventsToAdd.clear();
+ events.clear();
+ eventsToAdd.add(new LinkEvent(LINK_ADDED, NetTestTools.link("a", 1, "b", 2)));
+ event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ assertTrue("The list contained an unexpected number of events", events.size() == 2);
+ assertTrue("The first element is of the wrong type.",
+ events.get(0).type() == EDGE_PORT_REMOVED);
+ assertTrue("The second element is of the wrong type.",
+ events.get(1).type() == EDGE_PORT_REMOVED);
+
+ testPoint = events.get(0).subject();
+ referencePoint = NetTestTools.connectPoint("a", 1);
+ assertTrue("The port numbers of the first element are incorrect",
+ testPoint.port().toLong() == referencePoint.port().toLong());
+ assertTrue("The device id of the first element is incorrect.",
+ testPoint.deviceId().equals(referencePoint.deviceId()));
+
+ testPoint = events.get(1).subject();
+ referencePoint = NetTestTools.connectPoint("b", 2);
+ assertTrue("The port numbers of the second element are incorrect",
+ testPoint.port().toLong() == referencePoint.port().toLong());
+ assertTrue("The device id of the second element is incorrect.",
+ testPoint.deviceId().equals(referencePoint.deviceId()));
+
+ //Apparent duplicate of previous method tests removal when the elements have already been removed
+ eventsToAdd.clear();
+ events.clear();
+ eventsToAdd.add(new LinkEvent(LINK_ADDED, NetTestTools.link("a", 1, "b", 2)));
+ event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ assertTrue("The list should contain no events, the removed elements don't exist.", events.size() == 0);
+ }
+
+ @Test
+ public void testDeviceUpdates() {
+ //Setup
+
+ Device referenceDevice;
+ TopologyEvent event;
+ List<Event> eventsToAdd = Lists.newArrayList();
+ int numDevices = 10;
+ int numInfraPorts = 5;
+ totalPorts = 10;
+ defaultPopulator(numDevices, numInfraPorts);
+
+ //Test response to device added events
+ referenceDevice = NetTestTools.device("1");
+ eventsToAdd.add(new DeviceEvent(DEVICE_ADDED, referenceDevice,
+ new DefaultPort(referenceDevice, PortNumber.portNumber(1), true)));
+ event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ //Check that ports were populated correctly
+ assertTrue("Unexpected number of new ports added",
+ mgr.deviceService.getPorts(NetTestTools.did("1")).size() == 10);
+
+ //Check that of the ten ports the half that are infrastructure ports aren't added
+ assertEquals("Unexpected number of new edge ports added", (totalPorts - numInfraPorts), events.size());
+
+ for (int index = 0; index < numInfraPorts; index++) {
+ assertTrue("Unexpected type of event", events.get(index).type() == EDGE_PORT_ADDED);
+ }
+ //Names here are irrelevant, the first 5 ports are populated as infrastructure, 6-10 are edge
+ for (int index = 0; index < events.size(); index++) {
+ assertEquals("Port added had unexpected port number.",
+ events.get(index).subject().port(),
+ NetTestTools.connectPoint("a", index + numInfraPorts + 1).port());
+ }
+ events.clear();
+
+ //Repost the event to test repeated posts
+ testTopologyManager.listener.event(event);
+ assertEquals("The redundant notification should not have created additional notifications.",
+ 0, events.size());
+ //Calculate the size of the returned iterable of edge points.
+ Iterable<ConnectPoint> pts = mgr.getEdgePoints();
+ Iterator pointIterator = pts.iterator();
+ int count = 0;
+ for (; pointIterator.hasNext(); count++) {
+ pointIterator.next();
+ }
+ assertEquals("Unexpected number of edge points", totalPorts - numInfraPorts, count);
+ //Testing device removal
+ events.clear();
+ eventsToAdd.clear();
+ eventsToAdd.add(new DeviceEvent(DEVICE_REMOVED, referenceDevice,
+ new DefaultPort(referenceDevice, PortNumber.portNumber(1), true)));
+ event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ assertEquals("There should be five new events from removal of edge points",
+ totalPorts - numInfraPorts, events.size());
+ for (int index = 0; index < events.size(); index++) {
+ //Assert that the correct port numbers were removed in the correct order
+ assertEquals("Port removed had unexpected port number.",
+ events.get(index).subject().port(),
+ (NetTestTools.connectPoint("a", index + numInfraPorts + 1).port()));
+ //Assert that the events are of the correct type
+ assertEquals("Unexpected type of event", events.get(index).type(), EDGE_PORT_REMOVED);
+ }
+ events.clear();
+ //Rebroadcast event to check that it triggers no new behavior
+ testTopologyManager.listener.event(event);
+ assertEquals("Rebroadcast of removal event should not produce additional events",
+ 0, events.size());
+
+ //Testing device status change, changed from unavailable to available
+ events.clear();
+ eventsToAdd.clear();
+ //Make sure that the devicemanager shows the device as available.
+ addDevice(referenceDevice, "1", 5);
+ devices.put(referenceDevice.id(), referenceDevice);
+
+ eventsToAdd.add(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, referenceDevice));
+ event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+ //An earlier setup set half of the reference device ports to infrastructure
+ assertEquals("An unexpected number of events were generated.", totalPorts - numInfraPorts, events.size());
+ for (int i = 0; i < 5; i++) {
+ assertEquals("The event was not of the right type", events.get(i).type(), EDGE_PORT_ADDED);
+ }
+ events.clear();
+ testTopologyManager.listener.event(event);
+ assertEquals("No events should have been generated for a set of existing ports.", 0, events.size());
+
+ //Test removal when state changes when the device becomes unavailable
+
+ //Ensure that the deviceManager shows the device as unavailable
+ removeDevice(referenceDevice);
+ /*This variable copies the behavior of the topology by returning ports attached to an unavailable device
+ //this behavior is necessary for the following event to execute properly, if these statements are removed
+ no events will be generated since no ports will be provided in getPorts() to EdgeManager.
+ */
+ alwaysReturnPorts = true;
+ testTopologyManager.listener.event(event);
+ alwaysReturnPorts = false;
+ assertEquals("An unexpected number of events were created.", totalPorts - numInfraPorts, events.size());
+ for (int i = 0; i < 5; i++) {
+ EdgePortEvent edgeEvent = events.get(i);
+ assertEquals("The event is of an unexpected type.",
+ EdgePortEvent.Type.EDGE_PORT_REMOVED, edgeEvent.type());
+ assertEquals("The event pertains to an unexpected port", PortNumber.portNumber(i + numInfraPorts + 1),
+ edgeEvent.subject().port());
+ }
+ }
+
+ @Test
+ public void testInternalCache() {
+ List<Event> eventsToAdd = Lists.newArrayList();
+ int numDevices = 10;
+ //Number of infrastructure ports per device
+ int numPorts = 5;
+ //Total ports per device when requesting all devices
+ totalPorts = 10;
+ defaultPopulator(numDevices, numPorts);
+ for (int i = 0; i < numDevices; i++) {
+ Device newDevice = NetTestTools.device(Integer.toString(i));
+ devices.put(newDevice.id(), newDevice);
+ eventsToAdd.add(new DeviceEvent(DEVICE_ADDED, newDevice));
+ }
+ TopologyEvent event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ //Check all ports have correct designations
+ ConnectPoint testPoint;
+ for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) {
+ for (int portNum = 1; portNum <= totalPorts; portNum++) {
+ testPoint = NetTestTools.connectPoint(Integer.toString(deviceNum), portNum);
+ if (portNum <= numPorts) {
+ assertFalse("This should not be an edge point", mgr.isEdgePoint(testPoint));
+ } else {
+ assertTrue("This should be an edge point", mgr.isEdgePoint(testPoint));
+ }
+ }
+ }
+ int count = 0;
+ for (ConnectPoint ignored : mgr.getEdgePoints()) {
+ count++;
+ }
+ assertEquals("There are an unexpeceted number of edge points returned.",
+ (totalPorts - numPorts) * numDevices, count);
+ for (int deviceNumber = 0; deviceNumber < numDevices; deviceNumber++) {
+ count = 0;
+ for (ConnectPoint ignored : mgr.getEdgePoints(NetTestTools.did("1"))) {
+ count++;
+ }
+ assertEquals("This element has an unexpected number of edge points.", (totalPorts - numPorts), count);
+ }
+ }
+
+
+ @Test
+ public void testEmit() {
+ byte[] arr = new byte[10];
+ Device referenceDevice;
+ TopologyEvent event;
+ List<Event> eventsToAdd = Lists.newArrayList();
+ int numDevices = 10;
+ int numInfraPorts = 5;
+ totalPorts = 10;
+ defaultPopulator(numDevices, numInfraPorts);
+ for (byte byteIndex = 0; byteIndex < arr.length; byteIndex++) {
+ arr[byteIndex] = byteIndex;
+ }
+ for (int i = 0; i < numDevices; i++) {
+ referenceDevice = NetTestTools.device(Integer.toString(i));
+ eventsToAdd.add(new DeviceEvent(DEVICE_ADDED, referenceDevice,
+ new DefaultPort(referenceDevice, PortNumber.portNumber(1), true)));
+ }
+ event = new TopologyEvent(TOPOLOGY_CHANGED, null, eventsToAdd);
+ testTopologyManager.listener.event(event);
+
+ mgr.emitPacket(ByteBuffer.wrap(arr), Optional.<TrafficTreatment>empty());
+
+ assertEquals("There were an unexpected number of emitted packets",
+ (totalPorts - numInfraPorts) * numDevices, packets.size());
+ Iterator<OutboundPacket> packetIter = packets.iterator();
+ OutboundPacket packet;
+ while (packetIter.hasNext()) {
+ packet = packetIter.next();
+ assertEquals("The packet had an incorrect payload.", arr, packet.data().array());
+ }
+ //Start testing emission to a specific device
+ packets.clear();
+ mgr.emitPacket(NetTestTools.did(Integer.toString(1)), ByteBuffer.wrap(arr), Optional.<TrafficTreatment>empty());
+
+ assertEquals("Unexpected number of outbound packets were emitted.",
+ totalPorts - numInfraPorts, packets.size());
+ packetIter = packets.iterator();
+ while (packetIter.hasNext()) {
+ packet = packetIter.next();
+ assertEquals("The packet had an incorrect payload", arr, packet.data().array());
+ }
+ }
+
+
+ /**
+ * @param numDevices the number of devices to populate.
+ * @param numInfraPorts the number of ports to be set as infrastructure on each device, numbered base 0, ports 0
+ * through numInfraPorts - 1
+ */
+ private void defaultPopulator(int numDevices, int numInfraPorts) {
+ for (int device = 0; device < numDevices; device++) {
+ String str = Integer.toString(device);
+ Device deviceToAdd = NetTestTools.device(str);
+ devices.put(deviceToAdd.id(), deviceToAdd);
+ for (int port = 1; port <= numInfraPorts; port++) {
+ infrastructurePorts.add(NetTestTools.connectPoint(str, port));
+ }
+ }
+ }
+
+ /**
+ * Adds the specified device with the specified number of edge ports so long as it is less than the total ports.
+ *
+ * @param device The device to be added
+ * @param deviceName The name given to generate the devices DID
+ * @param numInfraPorts The number of ports to be added numbered 1 ... numInfraPorts
+ */
+ private void addDevice(Device device, String deviceName, int numInfraPorts) {
+ if (!devices.keySet().contains(device.id())) {
+ devices.put(device.id(), device);
+ for (int i = 1; i <= numInfraPorts && i <= totalPorts; i++) {
+ infrastructurePorts.add(NetTestTools.connectPoint(deviceName, i));
+ }
+ }
+ }
+
+ private void removeDevice(Device device) {
+ devices.remove(device.id());
+ }
+
+ private void removeInfraPort(ConnectPoint port) {
+ infrastructurePorts.remove(port);
+ }
+
+ private class TestTopologyManager extends TopologyServiceAdapter {
+ private TopologyListener listener;
+ private Set<ConnectPoint> infrastructurePorts;
+
+ public TestTopologyManager(Set<ConnectPoint> infrastructurePorts) {
+ this.infrastructurePorts = infrastructurePorts;
+ }
+
+ @Override
+ public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
+ return infrastructurePorts.contains(connectPoint);
+ }
+
+ @Override
+ public void addListener(TopologyListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void removeListener(TopologyListener listener) {
+ this.listener = null;
+ }
+ }
+
+ private class TestDeviceManager extends DeviceServiceAdapter {
+
+ private Map<DeviceId, Device> devices;
+
+ public TestDeviceManager(Map<DeviceId, Device> devices) {
+ this.devices = devices;
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ for (DeviceId id : devices.keySet()) {
+ if (id.equals(deviceId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ List<Port> ports = new ArrayList<>();
+ Device device = devices.get(deviceId);
+ if (device == null && !alwaysReturnPorts) {
+ return ports;
+ }
+ for (int portNum = 1; portNum <= totalPorts; portNum++) {
+ //String is generated using 'of:' + the passed name, this creates a
+ ports.add(new DefaultPort(device, PortNumber.portNumber(portNum), true));
+ }
+ return ports;
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return devices.values();
+ }
+ }
+
+ private class TestPacketManager extends PacketServiceAdapter {
+ @Override
+ public void emit(OutboundPacket packet) {
+ packets.add(packet);
+ }
+ }
+
+ private class TestListener implements EdgePortListener {
+ private List<EdgePortEvent> events;
+
+ public TestListener(List<EdgePortEvent> events) {
+ this.events = events;
+ }
+
+ @Override
+ public void event(EdgePortEvent event) {
+ events.add(event);
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
new file mode 100644
index 00000000..7ef8762c
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.flow.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.Device.Type;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowEntry.FlowEntryState;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleProvider;
+import org.onosproject.net.flow.FlowRuleProviderRegistry;
+import org.onosproject.net.flow.FlowRuleProviderService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.Instructions.MetadataInstruction;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.trivial.SimpleFlowRuleStore;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADDED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
+
+/**
+ * Test codifying the flow rule service & flow rule provider service contracts.
+ */
+public class FlowRuleManagerTest {
+
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final DeviceId DID = DeviceId.deviceId("of:001");
+ private static final int TIMEOUT = 10;
+ private static final Device DEV = new DefaultDevice(
+ PID, DID, Type.SWITCH, "", "", "", "", null);
+
+ private FlowRuleManager mgr;
+
+ protected FlowRuleService service;
+ protected FlowRuleProviderRegistry registry;
+ protected FlowRuleProviderService providerService;
+ protected TestProvider provider;
+ protected TestListener listener = new TestListener();
+ private ApplicationId appId;
+
+
+ @Before
+ public void setUp() {
+ mgr = new FlowRuleManager();
+ mgr.store = new SimpleFlowRuleStore();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ mgr.deviceService = new TestDeviceService();
+ mgr.coreService = new TestCoreService();
+ mgr.operationsService = MoreExecutors.newDirectExecutorService();
+ mgr.deviceInstallers = MoreExecutors.newDirectExecutorService();
+ mgr.cfgService = new ComponentConfigAdapter();
+ service = mgr;
+ registry = mgr;
+
+ mgr.activate(null);
+ mgr.addListener(listener);
+ provider = new TestProvider(PID);
+ providerService = registry.register(provider);
+ appId = new TestApplicationId(0, "FlowRuleManagerTest");
+ assertTrue("provider should be registered",
+ registry.getProviders().contains(provider.id()));
+ }
+
+ @After
+ public void tearDown() {
+ registry.unregister(provider);
+ assertFalse("provider should not be registered",
+ registry.getProviders().contains(provider.id()));
+ service.removeListener(listener);
+ mgr.deactivate();
+ injectEventDispatcher(mgr, null);
+ mgr.deviceService = null;
+ }
+
+ private FlowRule flowRule(int tsval, int trval) {
+ TestSelector ts = new TestSelector(tsval);
+ TestTreatment tr = new TestTreatment(trval);
+ return DefaultFlowRule.builder()
+ .forDevice(DID)
+ .withSelector(ts)
+ .withTreatment(tr)
+ .withPriority(10)
+ .fromApp(appId)
+ .makeTemporary(TIMEOUT)
+ .build();
+ }
+
+
+ private FlowRule addFlowRule(int hval) {
+ FlowRule rule = flowRule(hval, hval);
+ service.applyFlowRules(rule);
+
+ assertNotNull("rule should be found", service.getFlowEntries(DID));
+ return rule;
+ }
+
+ private void validateEvents(FlowRuleEvent.Type... events) {
+ if (events == null) {
+ assertTrue("events generated", listener.events.isEmpty());
+ }
+
+ int i = 0;
+ System.err.println("events :" + listener.events);
+ for (FlowRuleEvent e : listener.events) {
+ assertEquals("unexpected event", events[i], e.type());
+ i++;
+ }
+
+ assertEquals("mispredicted number of events",
+ events.length, listener.events.size());
+
+ listener.events.clear();
+ }
+
+ private int flowCount() {
+ return Sets.newHashSet(service.getFlowEntries(DID)).size();
+ }
+
+ @Test
+ public void getFlowEntries() {
+ assertTrue("store should be empty",
+ Sets.newHashSet(service.getFlowEntries(DID)).isEmpty());
+ FlowRule f1 = addFlowRule(1);
+ FlowRule f2 = addFlowRule(2);
+
+ FlowEntry fe1 = new DefaultFlowEntry(f1);
+ FlowEntry fe2 = new DefaultFlowEntry(f2);
+ assertEquals("2 rules should exist", 2, flowCount());
+
+ providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2));
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_ADDED, RULE_ADDED);
+
+ addFlowRule(1);
+ System.err.println("events :" + listener.events);
+ assertEquals("should still be 2 rules", 2, flowCount());
+
+ providerService.pushFlowMetrics(DID, ImmutableList.of(fe1));
+ validateEvents(RULE_UPDATED);
+ }
+
+ private boolean validateState(Map<FlowRule, FlowEntryState> expected) {
+ Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected);
+ Iterable<FlowEntry> rules = service.getFlowEntries(DID);
+ for (FlowEntry f : rules) {
+ assertTrue("Unexpected FlowRule " + f, expectedToCheck.containsKey(f));
+ assertEquals("FlowEntry" + f, expectedToCheck.get(f), f.state());
+ expectedToCheck.remove(f);
+ }
+ assertEquals(Collections.emptySet(), expectedToCheck.entrySet());
+ return true;
+ }
+
+ @Test
+ public void applyFlowRules() {
+
+ FlowRule r1 = flowRule(1, 1);
+ FlowRule r2 = flowRule(2, 2);
+ FlowRule r3 = flowRule(3, 3);
+
+ assertTrue("store should be empty",
+ Sets.newHashSet(service.getFlowEntries(DID)).isEmpty());
+ mgr.applyFlowRules(r1, r2, r3);
+ assertEquals("3 rules should exist", 3, flowCount());
+ assertTrue("Entries should be pending add.",
+ validateState(ImmutableMap.of(
+ r1, FlowEntryState.PENDING_ADD,
+ r2, FlowEntryState.PENDING_ADD,
+ r3, FlowEntryState.PENDING_ADD)));
+ }
+
+ @Test
+ public void removeFlowRules() {
+ FlowRule f1 = addFlowRule(1);
+ FlowRule f2 = addFlowRule(2);
+ FlowRule f3 = addFlowRule(3);
+ assertEquals("3 rules should exist", 3, flowCount());
+
+ FlowEntry fe1 = new DefaultFlowEntry(f1);
+ FlowEntry fe2 = new DefaultFlowEntry(f2);
+ FlowEntry fe3 = new DefaultFlowEntry(f3);
+ providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2, fe3));
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_ADDED, RULE_ADDED, RULE_ADDED);
+
+ mgr.removeFlowRules(f1, f2);
+ //removing from north, so no events generated
+ validateEvents(RULE_REMOVE_REQUESTED, RULE_REMOVE_REQUESTED);
+ assertEquals("3 rule should exist", 3, flowCount());
+ assertTrue("Entries should be pending remove.",
+ validateState(ImmutableMap.of(
+ f1, FlowEntryState.PENDING_REMOVE,
+ f2, FlowEntryState.PENDING_REMOVE,
+ f3, FlowEntryState.ADDED)));
+
+ mgr.removeFlowRules(f1);
+ assertEquals("3 rule should still exist", 3, flowCount());
+ }
+
+ @Test
+ public void flowRemoved() {
+
+ FlowRule f1 = addFlowRule(1);
+ FlowRule f2 = addFlowRule(2);
+ StoredFlowEntry fe1 = new DefaultFlowEntry(f1);
+ FlowEntry fe2 = new DefaultFlowEntry(f2);
+
+
+ providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2));
+ service.removeFlowRules(f1);
+
+ fe1.setState(FlowEntryState.REMOVED);
+
+
+
+ providerService.flowRemoved(fe1);
+
+
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED,
+ RULE_ADDED, RULE_REMOVE_REQUESTED, RULE_REMOVED);
+
+ providerService.flowRemoved(fe1);
+ validateEvents();
+
+ FlowRule f3 = flowRule(3, 3);
+ FlowEntry fe3 = new DefaultFlowEntry(f3);
+ service.applyFlowRules(f3);
+
+ providerService.pushFlowMetrics(DID, Collections.singletonList(fe3));
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADDED);
+
+ providerService.flowRemoved(fe3);
+ validateEvents();
+
+ }
+
+ @Test
+ public void flowMetrics() {
+ FlowRule f1 = flowRule(1, 1);
+ FlowRule f2 = flowRule(2, 2);
+ FlowRule f3 = flowRule(3, 3);
+
+ mgr.applyFlowRules(f1, f2, f3);
+
+ FlowEntry fe1 = new DefaultFlowEntry(f1);
+ FlowEntry fe2 = new DefaultFlowEntry(f2);
+
+ //FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
+ //FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+
+ providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2));
+
+ assertTrue("Entries should be added.",
+ validateState(ImmutableMap.of(
+ f1, FlowEntryState.ADDED,
+ f2, FlowEntryState.ADDED,
+ f3, FlowEntryState.PENDING_ADD)));
+
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_ADDED, RULE_ADDED);
+ }
+
+ @Test
+ public void extraneousFlow() {
+ FlowRule f1 = flowRule(1, 1);
+ FlowRule f2 = flowRule(2, 2);
+ FlowRule f3 = flowRule(3, 3);
+ mgr.applyFlowRules(f1, f2);
+
+// FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
+// FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+// FlowRule updatedF3 = flowRule(f3, FlowRuleState.ADDED);
+ FlowEntry fe1 = new DefaultFlowEntry(f1);
+ FlowEntry fe2 = new DefaultFlowEntry(f2);
+ FlowEntry fe3 = new DefaultFlowEntry(f3);
+
+
+ providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2, fe3));
+
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED, RULE_ADDED);
+
+ }
+
+ /*
+ * Tests whether a rule that was marked for removal but no flowRemoved was received
+ * is indeed removed at the next stats update.
+ */
+ @Test
+ public void flowMissingRemove() {
+ FlowRule f1 = flowRule(1, 1);
+ FlowRule f2 = flowRule(2, 2);
+ FlowRule f3 = flowRule(3, 3);
+
+// FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
+// FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+
+ FlowEntry fe1 = new DefaultFlowEntry(f1);
+ FlowEntry fe2 = new DefaultFlowEntry(f2);
+ mgr.applyFlowRules(f1, f2, f3);
+
+ mgr.removeFlowRules(f3);
+
+ providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2));
+
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_REMOVE_REQUESTED, RULE_ADDED, RULE_ADDED, RULE_REMOVED);
+
+ }
+
+ @Test
+ public void getByAppId() {
+ FlowRule f1 = flowRule(1, 1);
+ FlowRule f2 = flowRule(2, 2);
+ mgr.applyFlowRules(f1, f2);
+
+ assertTrue("should have two rules",
+ Lists.newLinkedList(mgr.getFlowRulesById(appId)).size() == 2);
+ }
+
+ @Test
+ public void removeByAppId() {
+ FlowRule f1 = flowRule(1, 1);
+ FlowRule f2 = flowRule(2, 2);
+ mgr.applyFlowRules(f1, f2);
+
+
+ mgr.removeFlowRulesById(appId);
+
+ //only check that we are in pending remove. Events and actual remove state will
+ // be set by flowRemoved call.
+ validateState(ImmutableMap.of(
+ f1, FlowEntryState.PENDING_REMOVE,
+ f2, FlowEntryState.PENDING_REMOVE));
+ }
+
+ private static class TestListener implements FlowRuleListener {
+ final List<FlowRuleEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(FlowRuleEvent event) {
+ events.add(event);
+ }
+ }
+
+ private static class TestDeviceService extends DeviceServiceAdapter {
+
+ @Override
+ public int getDeviceCount() {
+ return 1;
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return Collections.singletonList(DEV);
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return DEV;
+ }
+
+ @Override
+ public MastershipRole getRole(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ return null;
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return false;
+ }
+
+ @Override
+ public void addListener(DeviceListener listener) {
+ }
+
+ @Override
+ public void removeListener(DeviceListener listener) {
+ }
+
+ }
+
+ private class TestProvider extends AbstractProvider implements FlowRuleProvider {
+
+ protected TestProvider(ProviderId id) {
+ super(PID);
+ }
+
+ @Override
+ public void applyFlowRule(FlowRule... flowRules) {
+ }
+
+ @Override
+ public void removeFlowRule(FlowRule... flowRules) {
+ }
+
+ @Override
+ public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
+ }
+
+ @Override
+ public void executeBatch(FlowRuleBatchOperation batch) {
+ // TODO: need to call batchOperationComplete
+ }
+
+ private class TestInstallationFuture
+ implements ListenableFuture<CompletedBatchOperation> {
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public CompletedBatchOperation get()
+ throws InterruptedException, ExecutionException {
+ return new CompletedBatchOperation(true, Collections.<FlowRule>emptySet(), null);
+ }
+
+ @Override
+ public CompletedBatchOperation get(long timeout, TimeUnit unit)
+ throws InterruptedException,
+ ExecutionException, TimeoutException {
+ return new CompletedBatchOperation(true, Collections.<FlowRule>emptySet(), null);
+ }
+
+ @Override
+ public void addListener(Runnable task, Executor executor) {
+ if (isDone()) {
+ executor.execute(task);
+ }
+ }
+ }
+
+ }
+
+ private class TestSelector implements TrafficSelector {
+
+ //for controlling hashcode uniqueness;
+ private final int testval;
+
+ public TestSelector(int val) {
+ testval = val;
+ }
+
+ @Override
+ public Set<Criterion> criteria() {
+ return null;
+ }
+
+ @Override
+ public Criterion getCriterion(
+ org.onosproject.net.flow.criteria.Criterion.Type type) {
+ return null;
+ }
+
+ @Override
+ public int hashCode() {
+ return testval;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof TestSelector) {
+ return this.testval == ((TestSelector) o).testval;
+ }
+ return false;
+ }
+
+ }
+
+ private class TestTreatment implements TrafficTreatment {
+
+ //for controlling hashcode uniqueness;
+ private final int testval;
+
+ public TestTreatment(int val) {
+ testval = val;
+ }
+
+ @Override
+ public List<Instruction> deferred() {
+ return null;
+ }
+
+ @Override
+ public List<Instruction> immediate() {
+ return null;
+ }
+
+ @Override
+ public List<Instruction> allInstructions() {
+ return null;
+ }
+
+ @Override
+ public Instructions.TableTypeTransition tableTransition() {
+ return null;
+ }
+
+ @Override
+ public boolean clearedDeferred() {
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return testval;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof TestTreatment) {
+ return this.testval == ((TestTreatment) o).testval;
+ }
+ return false;
+ }
+
+ @Override
+ public MetadataInstruction writeMetadata() {
+ return null;
+ }
+
+ @Override
+ public Instructions.MeterInstruction metered() {
+ return null;
+ }
+
+ }
+
+ public class TestApplicationId extends DefaultApplicationId {
+ public TestApplicationId(int id, String name) {
+ super(id, name);
+ }
+ }
+
+ private class TestCoreService extends CoreServiceAdapter {
+
+ @Override
+ public IdGenerator getIdGenerator(String topic) {
+ return new IdGenerator() {
+ private AtomicLong counter = new AtomicLong(0);
+ @Override
+ public long getNewId() {
+ return counter.getAndIncrement();
+ }
+ };
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java
new file mode 100644
index 00000000..1e898d37
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/flowobjective/impl/FlowObjectiveCompositionTreeTest.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.flowobjective.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test FlowObjectiveCompositionTree.
+ */
+@Ignore
+public class FlowObjectiveCompositionTreeTest {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Before
+ public void setUp() {}
+
+ @After
+ public void tearDown() {}
+
+ /*@Test
+ public void testParallelComposition() {
+ FlowObjectiveCompositionTree policyTree = FlowObjectiveCompositionUtil.parsePolicyString("31+32");
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(IpPrefix.valueOf("1.0.0.0/24"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder().build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(0)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.1/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(1))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.2/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(2))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder().build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(0)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Parallel ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.3/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Parallel ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.3/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .remove();
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(IpPrefix.valueOf("1.0.0.0/24"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .remove();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder().build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(0)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .remove();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Parallel ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+ }
+
+ @Test
+ public void testSequentialComposition() {
+ FlowObjectiveCompositionTree policyTree = FlowObjectiveCompositionUtil.parsePolicyString("31>32");
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(IpPrefix.valueOf("0.0.0.0/2"))
+ .matchIPDst(IpPrefix.valueOf("3.0.0.0/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setIpDst(IpAddress.valueOf("2.0.0.1"))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(3)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("3.0.0.0/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setIpDst(IpAddress.valueOf("2.0.0.2"))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder().build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(0)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.1/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(1))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.2/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(2))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder().build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(0)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Sequential ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.3/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(IpPrefix.valueOf("0.0.0.0/1"))
+ .matchIPDst(IpPrefix.valueOf("3.0.0.0/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setIpDst(IpAddress.valueOf("2.0.0.3"))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(3)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Sequential ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.3/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .remove();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(IpPrefix.valueOf("0.0.0.0/1"))
+ .matchIPDst(IpPrefix.valueOf("3.0.0.0/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setIpDst(IpAddress.valueOf("2.0.0.3"))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(3)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .remove();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Sequential ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+ }
+
+ @Test
+ public void testOverrideComposition() {
+ FlowObjectiveCompositionTree policyTree = FlowObjectiveCompositionUtil.parsePolicyString("31/32");
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(IpPrefix.valueOf("1.0.0.0/32"))
+ .matchIPDst(IpPrefix.valueOf("2.0.0.1/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(31, "a"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.1/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(1))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.2/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(2))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+ helper(policyTree, forwardingObjective);
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder().build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(0)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Override ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.3/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .add();
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Override ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+
+ {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPDst(IpPrefix.valueOf("2.0.0.3/32"))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3))
+ .build();
+
+ ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
+ .fromApp(new DefaultApplicationId(32, "b"))
+ .makePermanent()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(1)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .remove();
+ helper(policyTree, forwardingObjective);
+ }
+
+ System.out.println("---------- Override ----------");
+ for (ForwardingObjective fo : policyTree.forwardTable.getForwardingObjectives()) {
+ System.out.println(forwardingObjectiveToString(fo));
+ }
+ }
+
+ private void helper(FlowObjectiveCompositionTree policyTree, ForwardingObjective forwardingObjective) {
+ log.info("before composition");
+ log.info("\t{}", forwardingObjectiveToString(forwardingObjective));
+ List<ForwardingObjective> forwardingObjectives
+ = policyTree.updateForward(forwardingObjective);
+ log.info("after composition");
+ for (ForwardingObjective fo : forwardingObjectives) {
+ log.info("\t{}", forwardingObjectiveToString(fo));
+ }
+ }*/
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java
new file mode 100644
index 00000000..ae7cc874
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.group.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.impl.DeviceManager;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.GroupProvider;
+import org.onosproject.net.group.GroupProviderRegistry;
+import org.onosproject.net.group.GroupProviderService;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.trivial.SimpleGroupStore;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Test codifying the group service & group provider service contracts.
+ */
+public class GroupManagerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "groupfoo");
+ private static final DeviceId DID = DeviceId.deviceId("of:001");
+
+ private GroupManager mgr;
+ private GroupService groupService;
+ private GroupProviderRegistry providerRegistry;
+ private TestGroupListener internalListener = new TestGroupListener();
+ private GroupListener listener = internalListener;
+ private TestGroupProvider internalProvider;
+ private GroupProvider provider;
+ private GroupProviderService providerService;
+ private ApplicationId appId;
+
+ @Before
+ public void setUp() {
+ mgr = new GroupManager();
+ groupService = mgr;
+ mgr.deviceService = new DeviceManager();
+ mgr.store = new SimpleGroupStore();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ providerRegistry = mgr;
+
+ mgr.activate();
+ mgr.addListener(listener);
+
+ internalProvider = new TestGroupProvider(PID);
+ provider = internalProvider;
+ providerService = providerRegistry.register(provider);
+ appId = new DefaultApplicationId(2, "org.groupmanager.test");
+ assertTrue("provider should be registered",
+ providerRegistry.getProviders().contains(provider.id()));
+ }
+
+ @After
+ public void tearDown() {
+ providerRegistry.unregister(provider);
+ assertFalse("provider should not be registered",
+ providerRegistry.getProviders().contains(provider.id()));
+ mgr.removeListener(listener);
+ mgr.deactivate();
+ injectEventDispatcher(mgr, null);
+ }
+
+ /**
+ * Tests group service north bound and south bound interfaces.
+ * The following operations are tested:
+ * a)Tests group creation before the device group AUDIT completes
+ * b)Tests initial device group AUDIT process
+ * c)Tests deletion process of any extraneous groups
+ * d)Tests execution of any pending group creation requests
+ * after the device group AUDIT completes
+ * e)Tests re-apply process of any missing groups
+ * f)Tests event notifications after receiving confirmation for
+ * any operations from data plane
+ * g)Tests group bucket modifications (additions and deletions)
+ * h)Tests group deletion
+ */
+ @Test
+ public void testGroupService() {
+ // Test Group creation before AUDIT process
+ testGroupCreationBeforeAudit();
+
+ // Test initial group audit process
+ testInitialAuditWithPendingGroupRequests();
+
+ // Test audit with extraneous and missing groups
+ testAuditWithExtraneousMissingGroups();
+
+ // Test audit with confirmed groups
+ testAuditWithConfirmedGroups();
+
+ // Test group add bucket operations
+ testAddBuckets();
+
+ // Test group remove bucket operations
+ testRemoveBuckets();
+
+ // Test group remove operations
+ testRemoveGroup();
+ }
+
+ // Test Group creation before AUDIT process
+ private void testGroupCreationBeforeAudit() {
+ PortNumber[] ports1 = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ PortNumber[] ports2 = {PortNumber.portNumber(41),
+ PortNumber.portNumber(42)};
+ GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+ List<GroupBucket> buckets = new ArrayList<>();
+ List<PortNumber> outPorts = new ArrayList<>();
+ outPorts.addAll(Arrays.asList(ports1));
+ outPorts.addAll(Arrays.asList(ports2));
+ for (PortNumber portNumber: outPorts) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(portNumber)
+ .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+ .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(106));
+ buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupBuckets = new GroupBuckets(buckets);
+ GroupDescription newGroupDesc = new DefaultGroupDescription(DID,
+ Group.Type.SELECT,
+ groupBuckets,
+ key,
+ null,
+ appId);
+ groupService.addGroup(newGroupDesc);
+ internalProvider.validate(DID, null);
+ assertEquals(null, groupService.getGroup(DID, key));
+ assertEquals(0, Iterables.size(groupService.getGroups(DID, appId)));
+ }
+
+ // Test initial AUDIT process with pending group requests
+ private void testInitialAuditWithPendingGroupRequests() {
+ PortNumber[] ports1 = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ PortNumber[] ports2 = {PortNumber.portNumber(41),
+ PortNumber.portNumber(42)};
+ GroupId gId1 = new DefaultGroupId(1);
+ Group group1 = createSouthboundGroupEntry(gId1,
+ Arrays.asList(ports1),
+ 0);
+ GroupId gId2 = new DefaultGroupId(2);
+ // Non zero reference count will make the group manager to queue
+ // the extraneous groups until reference count is zero.
+ Group group2 = createSouthboundGroupEntry(gId2,
+ Arrays.asList(ports2),
+ 2);
+ List<Group> groupEntries = Arrays.asList(group1, group2);
+ providerService.pushGroupMetrics(DID, groupEntries);
+ // First group metrics would trigger the device audit completion
+ // post which all pending group requests are also executed.
+ GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+ Group createdGroup = groupService.getGroup(DID, key);
+ int createdGroupId = createdGroup.id().id();
+ assertNotEquals(gId1.id(), createdGroupId);
+ assertNotEquals(gId2.id(), createdGroupId);
+
+ List<GroupOperation> expectedGroupOps = Arrays.asList(
+ GroupOperation.createDeleteGroupOperation(gId1,
+ Group.Type.SELECT),
+ GroupOperation.createAddGroupOperation(
+ createdGroup.id(),
+ Group.Type.SELECT,
+ createdGroup.buckets()));
+ internalProvider.validate(DID, expectedGroupOps);
+ }
+
+ // Test AUDIT process with extraneous groups and missing groups
+ private void testAuditWithExtraneousMissingGroups() {
+ PortNumber[] ports1 = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ PortNumber[] ports2 = {PortNumber.portNumber(41),
+ PortNumber.portNumber(42)};
+ GroupId gId1 = new DefaultGroupId(1);
+ Group group1 = createSouthboundGroupEntry(gId1,
+ Arrays.asList(ports1),
+ 0);
+ GroupId gId2 = new DefaultGroupId(2);
+ Group group2 = createSouthboundGroupEntry(gId2,
+ Arrays.asList(ports2),
+ 0);
+ List<Group> groupEntries = Arrays.asList(group1, group2);
+ providerService.pushGroupMetrics(DID, groupEntries);
+ GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+ Group createdGroup = groupService.getGroup(DID, key);
+ List<GroupOperation> expectedGroupOps = Arrays.asList(
+ GroupOperation.createDeleteGroupOperation(gId1,
+ Group.Type.SELECT),
+ GroupOperation.createDeleteGroupOperation(gId2,
+ Group.Type.SELECT),
+ GroupOperation.createAddGroupOperation(createdGroup.id(),
+ Group.Type.SELECT,
+ createdGroup.buckets()));
+ internalProvider.validate(DID, expectedGroupOps);
+ }
+
+ // Test AUDIT with confirmed groups
+ private void testAuditWithConfirmedGroups() {
+ GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+ Group createdGroup = groupService.getGroup(DID, key);
+ createdGroup = new DefaultGroup(createdGroup.id(),
+ DID,
+ Group.Type.SELECT,
+ createdGroup.buckets());
+ List<Group> groupEntries = Collections.singletonList(createdGroup);
+ providerService.pushGroupMetrics(DID, groupEntries);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_ADDED));
+ }
+
+ // Test group add bucket operations
+ private void testAddBuckets() {
+ GroupKey addKey = new DefaultGroupKey("group1AddBuckets".getBytes());
+
+ GroupKey prevKey = new DefaultGroupKey("group1BeforeAudit".getBytes());
+ Group createdGroup = groupService.getGroup(DID, prevKey);
+ List<GroupBucket> buckets = new ArrayList<>();
+ buckets.addAll(createdGroup.buckets().buckets());
+
+ PortNumber[] addPorts = {PortNumber.portNumber(51),
+ PortNumber.portNumber(52)};
+ List<PortNumber> outPorts;
+ outPorts = new ArrayList<PortNumber>();
+ outPorts.addAll(Arrays.asList(addPorts));
+ List<GroupBucket> addBuckets;
+ addBuckets = new ArrayList<GroupBucket>();
+ for (PortNumber portNumber: outPorts) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(portNumber)
+ .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+ .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(106));
+ addBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupAddBuckets = new GroupBuckets(addBuckets);
+ groupService.addBucketsToGroup(DID,
+ prevKey,
+ groupAddBuckets,
+ addKey,
+ appId);
+ GroupBuckets updatedBuckets = new GroupBuckets(buckets);
+ List<GroupOperation> expectedGroupOps = Collections.singletonList(
+ GroupOperation.createModifyGroupOperation(createdGroup.id(),
+ Group.Type.SELECT,
+ updatedBuckets));
+ internalProvider.validate(DID, expectedGroupOps);
+ Group existingGroup = groupService.getGroup(DID, addKey);
+ List<Group> groupEntries = Collections.singletonList(existingGroup);
+ providerService.pushGroupMetrics(DID, groupEntries);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATED));
+ }
+
+ // Test group remove bucket operations
+ private void testRemoveBuckets() {
+ GroupKey removeKey = new DefaultGroupKey("group1RemoveBuckets".getBytes());
+
+ GroupKey prevKey = new DefaultGroupKey("group1AddBuckets".getBytes());
+ Group createdGroup = groupService.getGroup(DID, prevKey);
+ List<GroupBucket> buckets = new ArrayList<>();
+ buckets.addAll(createdGroup.buckets().buckets());
+
+ PortNumber[] removePorts = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ List<PortNumber> outPorts = new ArrayList<>();
+ outPorts.addAll(Arrays.asList(removePorts));
+ List<GroupBucket> removeBuckets = new ArrayList<>();
+ for (PortNumber portNumber: outPorts) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(portNumber)
+ .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+ .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(106));
+ removeBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ buckets.remove(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupRemoveBuckets = new GroupBuckets(removeBuckets);
+ groupService.removeBucketsFromGroup(DID,
+ prevKey,
+ groupRemoveBuckets,
+ removeKey,
+ appId);
+ GroupBuckets updatedBuckets = new GroupBuckets(buckets);
+ List<GroupOperation> expectedGroupOps = Collections.singletonList(
+ GroupOperation.createModifyGroupOperation(createdGroup.id(),
+ Group.Type.SELECT,
+ updatedBuckets));
+ internalProvider.validate(DID, expectedGroupOps);
+ Group existingGroup = groupService.getGroup(DID, removeKey);
+ List<Group> groupEntries = Collections.singletonList(existingGroup);
+ providerService.pushGroupMetrics(DID, groupEntries);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATED));
+ }
+
+ // Test group remove operations
+ private void testRemoveGroup() {
+ GroupKey currKey = new DefaultGroupKey("group1RemoveBuckets".getBytes());
+ Group existingGroup = groupService.getGroup(DID, currKey);
+ groupService.removeGroup(DID, currKey, appId);
+ List<GroupOperation> expectedGroupOps = Collections.singletonList(
+ GroupOperation.createDeleteGroupOperation(existingGroup.id(),
+ Group.Type.SELECT));
+ internalProvider.validate(DID, expectedGroupOps);
+ List<Group> groupEntries = Collections.emptyList();
+ providerService.pushGroupMetrics(DID, groupEntries);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_REMOVED));
+ }
+
+ /**
+ * Test GroupOperationFailure function in Group Manager.
+ * a)GroupAddFailure
+ * b)GroupUpdateFailure
+ * c)GroupRemoteFailure
+ */
+ @Test
+ public void testGroupOperationFailure() {
+ PortNumber[] ports1 = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ PortNumber[] ports2 = {PortNumber.portNumber(41),
+ PortNumber.portNumber(42)};
+ // Test Group creation before AUDIT process
+ GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+ List<GroupBucket> buckets = new ArrayList<>();
+ List<PortNumber> outPorts = new ArrayList<>();
+ outPorts.addAll(Arrays.asList(ports1));
+ outPorts.addAll(Arrays.asList(ports2));
+ for (PortNumber portNumber: outPorts) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(portNumber)
+ .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+ .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(106));
+ buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupBuckets = new GroupBuckets(buckets);
+ GroupDescription newGroupDesc = new DefaultGroupDescription(DID,
+ Group.Type.SELECT,
+ groupBuckets,
+ key,
+ null,
+ appId);
+ groupService.addGroup(newGroupDesc);
+
+ // Test initial group audit process
+ GroupId gId1 = new DefaultGroupId(1);
+ Group group1 = createSouthboundGroupEntry(gId1,
+ Arrays.asList(ports1),
+ 0);
+ GroupId gId2 = new DefaultGroupId(2);
+ // Non zero reference count will make the group manager to queue
+ // the extraneous groups until reference count is zero.
+ Group group2 = createSouthboundGroupEntry(gId2,
+ Arrays.asList(ports2),
+ 2);
+ List<Group> groupEntries = Arrays.asList(group1, group2);
+ providerService.pushGroupMetrics(DID, groupEntries);
+ Group createdGroup = groupService.getGroup(DID, key);
+
+ // Group Add failure test
+ GroupOperation groupAddOp = GroupOperation.
+ createAddGroupOperation(createdGroup.id(),
+ createdGroup.type(),
+ createdGroup.buckets());
+ providerService.groupOperationFailed(DID, groupAddOp);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_ADD_FAILED));
+
+ // Group Mod failure test
+ groupService.addGroup(newGroupDesc);
+ createdGroup = groupService.getGroup(DID, key);
+ assertNotNull(createdGroup);
+
+ GroupOperation groupModOp = GroupOperation.
+ createModifyGroupOperation(createdGroup.id(),
+ createdGroup.type(),
+ createdGroup.buckets());
+ providerService.groupOperationFailed(DID, groupModOp);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATE_FAILED));
+
+ // Group Delete failure test
+ groupService.addGroup(newGroupDesc);
+ createdGroup = groupService.getGroup(DID, key);
+ assertNotNull(createdGroup);
+
+ GroupOperation groupDelOp = GroupOperation.
+ createDeleteGroupOperation(createdGroup.id(),
+ createdGroup.type());
+ providerService.groupOperationFailed(DID, groupDelOp);
+ internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_REMOVE_FAILED));
+
+ }
+
+ private Group createSouthboundGroupEntry(GroupId gId,
+ List<PortNumber> ports,
+ long referenceCount) {
+ List<PortNumber> outPorts = new ArrayList<>();
+ outPorts.addAll(ports);
+
+ List<GroupBucket> buckets = new ArrayList<>();
+ for (PortNumber portNumber: outPorts) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(portNumber)
+ .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+ .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(106));
+ buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupBuckets = new GroupBuckets(buckets);
+ StoredGroupEntry group = new DefaultGroup(
+ gId, DID, Group.Type.SELECT, groupBuckets);
+ group.setReferenceCount(referenceCount);
+ return group;
+ }
+
+ private static class TestGroupListener implements GroupListener {
+ final List<GroupEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(GroupEvent event) {
+ events.add(event);
+ }
+
+ public void validateEvent(List<GroupEvent.Type> expectedEvents) {
+ int i = 0;
+ System.err.println("events :" + events);
+ for (GroupEvent e : events) {
+ assertEquals("unexpected event", expectedEvents.get(i), e.type());
+ i++;
+ }
+ assertEquals("mispredicted number of events",
+ expectedEvents.size(), events.size());
+ events.clear();
+ }
+ }
+
+ private class TestGroupProvider
+ extends AbstractProvider implements GroupProvider {
+ DeviceId lastDeviceId;
+ List<GroupOperation> groupOperations = new ArrayList<>();
+
+ protected TestGroupProvider(ProviderId id) {
+ super(id);
+ }
+
+ @Override
+ public void performGroupOperation(DeviceId deviceId,
+ GroupOperations groupOps) {
+ lastDeviceId = deviceId;
+ groupOperations.addAll(groupOps.operations());
+ }
+
+ public void validate(DeviceId expectedDeviceId,
+ List<GroupOperation> expectedGroupOps) {
+ if (expectedGroupOps == null) {
+ assertTrue("events generated", groupOperations.isEmpty());
+ return;
+ }
+
+ assertEquals(lastDeviceId, expectedDeviceId);
+ assertTrue((this.groupOperations.containsAll(expectedGroupOps) &&
+ expectedGroupOps.containsAll(groupOperations)));
+
+ groupOperations.clear();
+ lastDeviceId = null;
+ }
+
+ }
+
+}
+
+
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java
new file mode 100644
index 00000000..e7f14b5d
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/BasicHostOperatorTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.host.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.config.basics.BasicHostConfig;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+public class BasicHostOperatorTest {
+ private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01");
+ private static final VlanId VLAN = VlanId.vlanId((short) 10);
+ private static final IpAddress IP = IpAddress.valueOf("10.0.0.1");
+
+ private static final HostId ID = HostId.hostId(MAC);
+ private static final HostLocation LOC = new HostLocation(
+ DeviceId.deviceId("of:foo"),
+ PortNumber.portNumber(100),
+ 123L
+ );
+ private static final HostDescription HOST = new DefaultHostDescription(MAC, VLAN, LOC, IP);
+
+ private final ConfigApplyDelegate delegate = new ConfigApplyDelegate() {
+ @Override
+ public void onApply(Config config) {
+ }
+ };
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private static final BasicHostConfig BHC = new BasicHostConfig();
+ private static final String NAME = "testhost";
+ private static final double LAT = 40.96;
+
+ @Before
+ public void setUp() {
+ BHC.init(ID, "test", JsonNodeFactory.instance.objectNode(), mapper, delegate);
+ BHC.name(NAME).latitude(40.96);
+ }
+
+ @Test
+ public void testDescOps() {
+ HostDescription desc = BasicHostOperator.combine(BHC, HOST);
+ assertEquals(NAME, desc.annotations().value(AnnotationKeys.NAME));
+ assertEquals(null, desc.annotations().value(AnnotationKeys.LONGITUDE));
+ assertEquals(String.valueOf(LAT), desc.annotations().value(AnnotationKeys.LATITUDE));
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
new file mode 100644
index 00000000..dbb807f8
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.host.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_MOVED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_UPDATED;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.event.Event;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.config.NetworkConfigServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.host.PortAddresses;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.trivial.SimpleHostStore;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+/**
+ * Test codifying the host service & host provider service contracts.
+ */
+public class HostManagerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+
+ private static final VlanId VLAN1 = VlanId.vlanId((short) 1);
+ private static final VlanId VLAN2 = VlanId.vlanId((short) 2);
+ private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01");
+ private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
+ private static final MacAddress MAC3 = MacAddress.valueOf("00:00:33:00:00:03");
+ private static final MacAddress MAC4 = MacAddress.valueOf("00:00:44:00:00:04");
+ private static final HostId HID1 = HostId.hostId(MAC1, VLAN1);
+ private static final HostId HID2 = HostId.hostId(MAC2, VLAN1);
+ private static final HostId HID3 = HostId.hostId(MAC3, VLAN1);
+ private static final HostId HID4 = HostId.hostId(MAC4, VLAN1);
+
+ private static final IpAddress IP1 = IpAddress.valueOf("10.0.0.1");
+ private static final IpAddress IP2 = IpAddress.valueOf("10.0.0.2");
+ private static final IpAddress IP3 = IpAddress.valueOf("2001::1");
+ private static final IpAddress IP4 = IpAddress.valueOf("2001::2");
+
+ private static final DeviceId DID1 = DeviceId.deviceId("of:001");
+ private static final DeviceId DID2 = DeviceId.deviceId("of:002");
+ private static final PortNumber P1 = PortNumber.portNumber(100);
+ private static final PortNumber P2 = PortNumber.portNumber(200);
+ private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L);
+ private static final HostLocation LOC2 = new HostLocation(DID1, P2, 123L);
+ private static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+ private static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+
+ private static final InterfaceIpAddress IA1 =
+ new InterfaceIpAddress(IpAddress.valueOf("10.1.1.1"),
+ IpPrefix.valueOf("10.1.1.0/24"));
+ private static final InterfaceIpAddress IA2 =
+ new InterfaceIpAddress(IpAddress.valueOf("10.2.2.2"),
+ IpPrefix.valueOf("10.2.0.0/16"));
+ private static final InterfaceIpAddress IA3 =
+ new InterfaceIpAddress(IpAddress.valueOf("10.3.3.3"),
+ IpPrefix.valueOf("10.3.3.0/24"));
+ private static final InterfaceIpAddress IA4 =
+ new InterfaceIpAddress(IpAddress.valueOf("2001:100::1"),
+ IpPrefix.valueOf("2001:100::/56"));
+ private static final InterfaceIpAddress IA5 =
+ new InterfaceIpAddress(IpAddress.valueOf("2001:200::1"),
+ IpPrefix.valueOf("2001:200::/48"));
+ private static final InterfaceIpAddress IA6 =
+ new InterfaceIpAddress(IpAddress.valueOf("2001:300::1"),
+ IpPrefix.valueOf("2001:300::/56"));
+
+ private HostManager mgr;
+
+ protected TestListener listener = new TestListener();
+ protected HostProviderRegistry registry;
+ protected TestHostProvider provider;
+ protected HostProviderService providerService;
+
+ @Before
+ public void setUp() {
+ mgr = new HostManager();
+ mgr.store = new SimpleHostStore();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ registry = mgr;
+ mgr.networkConfigService = new TestNetworkConfigService();
+ mgr.activate();
+
+ mgr.addListener(listener);
+
+ provider = new TestHostProvider();
+ providerService = registry.register(provider);
+ assertTrue("provider should be registered",
+ registry.getProviders().contains(provider.id()));
+ }
+
+ @After
+ public void tearDown() {
+ registry.unregister(provider);
+ assertFalse("provider should not be registered",
+ registry.getProviders().contains(provider.id()));
+
+ mgr.removeListener(listener);
+ mgr.deactivate();
+ injectEventDispatcher(mgr, null);
+ }
+
+ private void detect(HostId hid, MacAddress mac, VlanId vlan,
+ HostLocation loc, IpAddress ip) {
+ HostDescription descr = new DefaultHostDescription(mac, vlan, loc, ip);
+ providerService.hostDetected(hid, descr);
+ assertNotNull("host should be found", mgr.getHost(hid));
+ }
+
+ private void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("wrong events received", types.length, listener.events.size());
+ for (Event event : listener.events) {
+ assertEquals("incorrect event type", types[i], event.type());
+ i++;
+ }
+ listener.events.clear();
+ }
+
+ @Test
+ public void hostDetected() {
+ assertNull("host shouldn't be found", mgr.getHost(HID1));
+
+ // host addition
+ detect(HID1, MAC1, VLAN1, LOC1, IP1);
+ assertEquals("exactly one should be found", 1, mgr.getHostCount());
+ detect(HID2, MAC2, VLAN2, LOC2, IP1);
+ assertEquals("two hosts should be found", 2, mgr.getHostCount());
+ validateEvents(HOST_ADDED, HOST_ADDED);
+
+ // host motion
+ detect(HID1, MAC1, VLAN1, LOC2, IP1);
+ validateEvents(HOST_MOVED);
+ assertEquals("only two hosts should be found", 2, mgr.getHostCount());
+
+ // host update
+ detect(HID1, MAC1, VLAN1, LOC2, IP2);
+ validateEvents(HOST_UPDATED);
+ assertEquals("only two hosts should be found", 2, mgr.getHostCount());
+ }
+
+ @Test
+ public void hostDetectedIPv6() {
+ assertNull("host shouldn't be found", mgr.getHost(HID3));
+
+ // host addition
+ detect(HID3, MAC3, VLAN1, LOC1, IP3);
+ assertEquals("exactly one should be found", 1, mgr.getHostCount());
+ detect(HID4, MAC4, VLAN2, LOC2, IP3);
+ assertEquals("two hosts should be found", 2, mgr.getHostCount());
+ validateEvents(HOST_ADDED, HOST_ADDED);
+
+ // host motion
+ detect(HID3, MAC3, VLAN1, LOC2, IP3);
+ validateEvents(HOST_MOVED);
+ assertEquals("only two hosts should be found", 2, mgr.getHostCount());
+
+ // host update
+ detect(HID3, MAC3, VLAN1, LOC2, IP4);
+ validateEvents(HOST_UPDATED);
+ assertEquals("only two hosts should be found", 2, mgr.getHostCount());
+ }
+
+ @Test
+ public void hostVanished() {
+ detect(HID1, MAC1, VLAN1, LOC1, IP1);
+ providerService.hostVanished(HID1);
+ validateEvents(HOST_ADDED, HOST_REMOVED);
+
+ assertNull("host should have been removed", mgr.getHost(HID1));
+ }
+
+ @Test
+ public void hostVanishedIPv6() {
+ detect(HID3, MAC3, VLAN1, LOC1, IP3);
+ providerService.hostVanished(HID3);
+ validateEvents(HOST_ADDED, HOST_REMOVED);
+
+ assertNull("host should have been removed", mgr.getHost(HID3));
+ }
+
+ private void validateHosts(
+ String msg, Iterable<Host> hosts, HostId... ids) {
+ Set<HostId> hids = Sets.newHashSet(ids);
+ for (Host h : hosts) {
+ assertTrue(msg, hids.remove(h.id()));
+ }
+ assertTrue("expected hosts not fetched from store", hids.isEmpty());
+ }
+
+ @Test
+ public void getHosts() {
+ detect(HID1, MAC1, VLAN1, LOC1, IP1);
+ detect(HID2, MAC2, VLAN1, LOC2, IP2);
+
+ validateHosts("host not properly stored", mgr.getHosts(), HID1, HID2);
+ validateHosts("can't get hosts by VLAN", mgr.getHostsByVlan(VLAN1), HID1, HID2);
+ validateHosts("can't get hosts by MAC", mgr.getHostsByMac(MAC1), HID1);
+ validateHosts("can't get hosts by IP", mgr.getHostsByIp(IP1), HID1);
+ validateHosts("can't get hosts by location", mgr.getConnectedHosts(LOC1), HID1);
+ assertTrue("incorrect host location", mgr.getConnectedHosts(DID2).isEmpty());
+ }
+
+ @Test
+ public void getHostsIPv6() {
+ detect(HID3, MAC3, VLAN1, LOC1, IP3);
+ detect(HID4, MAC4, VLAN1, LOC2, IP4);
+
+ validateHosts("host not properly stored", mgr.getHosts(), HID3, HID4);
+ validateHosts("can't get hosts by VLAN", mgr.getHostsByVlan(VLAN1), HID3, HID4);
+ validateHosts("can't get hosts by MAC", mgr.getHostsByMac(MAC3), HID3);
+ validateHosts("can't get hosts by IP", mgr.getHostsByIp(IP3), HID3);
+ validateHosts("can't get hosts by location", mgr.getConnectedHosts(LOC1), HID3);
+ assertTrue("incorrect host location", mgr.getConnectedHosts(DID2).isEmpty());
+ }
+
+ private static class TestHostProvider extends AbstractProvider
+ implements HostProvider {
+
+ protected TestHostProvider() {
+ super(PID);
+ }
+
+ @Override
+ public ProviderId id() {
+ return PID;
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ }
+
+ }
+
+ private static class TestListener implements HostListener {
+
+ protected List<HostEvent> events = Lists.newArrayList();
+
+ @Override
+ public void event(HostEvent event) {
+ events.add(event);
+ }
+
+ }
+
+ @Test
+ public void bindAddressesToPort() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ // Add some more addresses and check that they're added correctly
+ PortAddresses add2 =
+ new PortAddresses(CP1, Sets.newHashSet(IA3), null,
+ VlanId.vlanId((short) 2));
+
+ mgr.bindAddressesToPort(add2);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(2, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ assertTrue(storedAddresses.contains(add2));
+
+ PortAddresses add3 = new PortAddresses(CP1, null, MAC2, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add3);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(3, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ assertTrue(storedAddresses.contains(add2));
+ assertTrue(storedAddresses.contains(add3));
+ }
+
+ @Test
+ public void bindAddressesToPortIPv6() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ // Add some more addresses and check that they're added correctly
+ PortAddresses add2 =
+ new PortAddresses(CP1, Sets.newHashSet(IA6), null,
+ VlanId.vlanId((short) 2));
+
+ mgr.bindAddressesToPort(add2);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(2, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ assertTrue(storedAddresses.contains(add2));
+
+ PortAddresses add3 = new PortAddresses(CP1, null, MAC4, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add3);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(3, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ assertTrue(storedAddresses.contains(add2));
+ assertTrue(storedAddresses.contains(add3));
+ }
+
+ @Test
+ public void unbindAddressesFromPort() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ PortAddresses rem1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA1), null, VlanId.NONE);
+
+ mgr.unbindAddressesFromPort(rem1);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ // It shouldn't have been removed because it didn't match the originally
+ // submitted address object
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ mgr.unbindAddressesFromPort(add1);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertTrue(storedAddresses.isEmpty());
+ }
+
+ @Test
+ public void unbindAddressesFromPortIPv6() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ PortAddresses rem1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA4), null, VlanId.NONE);
+
+ mgr.unbindAddressesFromPort(rem1);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ // It shouldn't have been removed because it didn't match the originally
+ // submitted address object
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ mgr.unbindAddressesFromPort(add1);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertTrue(storedAddresses.isEmpty());
+ }
+
+ @Test
+ public void clearAddresses() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ mgr.clearAddresses(CP1);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertTrue(storedAddresses.isEmpty());
+ }
+
+ @Test
+ public void clearAddressesIPv6() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+
+ mgr.clearAddresses(CP1);
+ storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertTrue(storedAddresses.isEmpty());
+ }
+
+ @Test
+ public void getAddressBindingsForPort() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ }
+
+ @Test
+ public void getAddressBindingsForPortIPv6() {
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ }
+
+ @Test
+ public void getAddressBindings() {
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindings();
+
+ assertTrue(storedAddresses.isEmpty());
+
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+
+ storedAddresses = mgr.getAddressBindings();
+
+ assertTrue(storedAddresses.size() == 1);
+
+ PortAddresses add2 =
+ new PortAddresses(CP2, Sets.newHashSet(IA3), MAC2, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add2);
+
+ storedAddresses = mgr.getAddressBindings();
+
+ assertTrue(storedAddresses.size() == 2);
+ assertTrue(storedAddresses.equals(Sets.newHashSet(add1, add2)));
+ }
+
+ @Test
+ public void getAddressBindingsIPv6() {
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindings();
+
+ assertTrue(storedAddresses.isEmpty());
+
+ PortAddresses add1 =
+ new PortAddresses(CP1, Sets.newHashSet(IA4, IA5), MAC3, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add1);
+
+ storedAddresses = mgr.getAddressBindings();
+
+ assertTrue(storedAddresses.size() == 1);
+
+ PortAddresses add2 =
+ new PortAddresses(CP2, Sets.newHashSet(IA5), MAC4, VlanId.NONE);
+
+ mgr.bindAddressesToPort(add2);
+
+ storedAddresses = mgr.getAddressBindings();
+
+ assertTrue(storedAddresses.size() == 2);
+ assertTrue(storedAddresses.equals(Sets.newHashSet(add1, add2)));
+ }
+
+ private class TestNetworkConfigService extends NetworkConfigServiceAdapter {
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java
new file mode 100644
index 00000000..d6ff473a
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.host.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import org.junit.After;
+import org.junit.Test;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.host.PortAddresses;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class HostMonitorTest {
+
+ private static final IpAddress TARGET_IP_ADDR =
+ IpAddress.valueOf("10.0.0.1");
+ private static final IpAddress SOURCE_ADDR =
+ IpAddress.valueOf("10.0.0.99");
+ private static final InterfaceIpAddress IA1 =
+ new InterfaceIpAddress(SOURCE_ADDR, IpPrefix.valueOf("10.0.0.0/24"));
+ private MacAddress sourceMac = MacAddress.valueOf(1L);
+
+ private HostMonitor hostMonitor;
+
+ @After
+ public void shutdown() {
+ hostMonitor.shutdown();
+ }
+
+ @Test
+ public void testMonitorHostExists() throws Exception {
+ ProviderId id = new ProviderId("fake://", "id");
+
+ Host host = createMock(Host.class);
+ expect(host.providerId()).andReturn(id);
+ replay(host);
+
+ HostManager hostManager = createMock(HostManager.class);
+ expect(hostManager.getHostsByIp(TARGET_IP_ADDR))
+ .andReturn(Collections.singleton(host));
+ replay(hostManager);
+
+ HostProvider hostProvider = createMock(HostProvider.class);
+ expect(hostProvider.id()).andReturn(id).anyTimes();
+ hostProvider.triggerProbe(host);
+ expectLastCall().once();
+ replay(hostProvider);
+
+ hostMonitor = new HostMonitor(null, hostManager, null);
+
+ hostMonitor.registerHostProvider(hostProvider);
+ hostMonitor.addMonitoringFor(TARGET_IP_ADDR);
+
+ hostMonitor.run(null);
+
+ verify(hostProvider);
+ }
+
+ @Test
+ public void testMonitorHostDoesNotExist() throws Exception {
+
+ HostManager hostManager = createMock(HostManager.class);
+
+ DeviceId devId = DeviceId.deviceId("fake");
+
+ Device device = createMock(Device.class);
+ expect(device.id()).andReturn(devId).anyTimes();
+ replay(device);
+
+ PortNumber portNum = PortNumber.portNumber(1L);
+
+ Port port = createMock(Port.class);
+ expect(port.number()).andReturn(portNum).anyTimes();
+ replay(port);
+
+ TestDeviceService deviceService = new TestDeviceService();
+ deviceService.addDevice(device, Collections.singleton(port));
+
+ ConnectPoint cp = new ConnectPoint(devId, portNum);
+ PortAddresses pa =
+ new PortAddresses(cp, Collections.singleton(IA1), sourceMac, VlanId.NONE);
+
+ expect(hostManager.getHostsByIp(TARGET_IP_ADDR))
+ .andReturn(Collections.emptySet()).anyTimes();
+ replay(hostManager);
+
+ InterfaceService interfaceService = createMock(InterfaceService.class);
+ expect(interfaceService.getMatchingInterface(TARGET_IP_ADDR))
+ .andReturn(new Interface(cp, Collections.singleton(IA1), sourceMac, VlanId.NONE))
+ .anyTimes();
+ replay(interfaceService);
+
+ TestPacketService packetService = new TestPacketService();
+
+
+ // Run the test
+ hostMonitor = new HostMonitor(packetService, hostManager, interfaceService);
+
+ hostMonitor.addMonitoringFor(TARGET_IP_ADDR);
+ hostMonitor.run(null);
+
+
+ // Check that a packet was sent to our PacketService and that it has
+ // the properties we expect
+ assertEquals(1, packetService.packets.size());
+ OutboundPacket packet = packetService.packets.get(0);
+
+ // Check the output port is correct
+ assertEquals(1, packet.treatment().immediate().size());
+ Instruction instruction = packet.treatment().immediate().get(0);
+ assertTrue(instruction instanceof OutputInstruction);
+ OutputInstruction oi = (OutputInstruction) instruction;
+ assertEquals(portNum, oi.port());
+
+ // Check the output packet is correct (well the important bits anyway)
+ final byte[] pktData = new byte[packet.data().remaining()];
+ packet.data().get(pktData);
+ Ethernet eth = Ethernet.deserializer().deserialize(pktData, 0, pktData.length);
+ assertEquals(Ethernet.VLAN_UNTAGGED, eth.getVlanID());
+ ARP arp = (ARP) eth.getPayload();
+ assertArrayEquals(SOURCE_ADDR.toOctets(),
+ arp.getSenderProtocolAddress());
+ assertArrayEquals(sourceMac.toBytes(),
+ arp.getSenderHardwareAddress());
+ assertArrayEquals(TARGET_IP_ADDR.toOctets(),
+ arp.getTargetProtocolAddress());
+ }
+
+ @Test
+ public void testMonitorHostDoesNotExistWithVlan() throws Exception {
+
+ HostManager hostManager = createMock(HostManager.class);
+
+ DeviceId devId = DeviceId.deviceId("fake");
+ short vlan = 5;
+
+ Device device = createMock(Device.class);
+ expect(device.id()).andReturn(devId).anyTimes();
+ replay(device);
+
+ PortNumber portNum = PortNumber.portNumber(1L);
+
+ Port port = createMock(Port.class);
+ expect(port.number()).andReturn(portNum).anyTimes();
+ replay(port);
+
+ TestDeviceService deviceService = new TestDeviceService();
+ deviceService.addDevice(device, Collections.singleton(port));
+
+ ConnectPoint cp = new ConnectPoint(devId, portNum);
+ PortAddresses pa =
+ new PortAddresses(cp, Collections.singleton(IA1), sourceMac,
+ VlanId.vlanId(vlan));
+
+ expect(hostManager.getHostsByIp(TARGET_IP_ADDR))
+ .andReturn(Collections.emptySet()).anyTimes();
+ replay(hostManager);
+
+ InterfaceService interfaceService = createMock(InterfaceService.class);
+ expect(interfaceService.getMatchingInterface(TARGET_IP_ADDR))
+ .andReturn(new Interface(cp, Collections.singleton(IA1), sourceMac, VlanId.vlanId(vlan)))
+ .anyTimes();
+ replay(interfaceService);
+
+ TestPacketService packetService = new TestPacketService();
+
+
+ // Run the test
+ hostMonitor = new HostMonitor(packetService, hostManager, interfaceService);
+
+ hostMonitor.addMonitoringFor(TARGET_IP_ADDR);
+ hostMonitor.run(null);
+
+
+ // Check that a packet was sent to our PacketService and that it has
+ // the properties we expect
+ assertEquals(1, packetService.packets.size());
+ OutboundPacket packet = packetService.packets.get(0);
+
+ // Check the output port is correct
+ assertEquals(1, packet.treatment().immediate().size());
+ Instruction instruction = packet.treatment().immediate().get(0);
+ assertTrue(instruction instanceof OutputInstruction);
+ OutputInstruction oi = (OutputInstruction) instruction;
+ assertEquals(portNum, oi.port());
+
+ // Check the output packet is correct (well the important bits anyway)
+ final byte[] pktData = new byte[packet.data().remaining()];
+ packet.data().get(pktData);
+ Ethernet eth = Ethernet.deserializer().deserialize(pktData, 0, pktData.length);
+ assertEquals(vlan, eth.getVlanID());
+ ARP arp = (ARP) eth.getPayload();
+ assertArrayEquals(SOURCE_ADDR.toOctets(),
+ arp.getSenderProtocolAddress());
+ assertArrayEquals(sourceMac.toBytes(),
+ arp.getSenderHardwareAddress());
+ assertArrayEquals(TARGET_IP_ADDR.toOctets(),
+ arp.getTargetProtocolAddress());
+ }
+
+ class TestPacketService extends PacketServiceAdapter {
+
+ List<OutboundPacket> packets = new ArrayList<>();
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ packets.add(packet);
+ }
+ }
+
+ class TestDeviceService extends DeviceServiceAdapter {
+
+ List<Device> devices = Lists.newArrayList();
+ Multimap<DeviceId, Port> devicePorts = HashMultimap.create();
+
+ void addDevice(Device device, Set<Port> ports) {
+ devices.add(device);
+ for (Port p : ports) {
+ devicePorts.put(device.id(), p);
+ }
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return 0;
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return devices;
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public MastershipRole getRole(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ List<Port> ports = Lists.newArrayList();
+ for (Port p : devicePorts.get(deviceId)) {
+ ports.add(p);
+ }
+ return ports;
+ }
+
+ @Override
+ public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ return null;
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return false;
+ }
+
+ @Override
+ public void addListener(DeviceListener listener) {
+ }
+
+ @Override
+ public void removeListener(DeviceListener listener) {
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java
new file mode 100644
index 00000000..d34143c9
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent;
+
+import java.util.Collection;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.onosproject.net.EdgeLink;
+import org.onosproject.net.Link;
+
+/**
+ * Matcher to determine if a Collection of Links contains a path between a source
+ * and a destination.
+ */
+public class LinksHaveEntryWithSourceDestinationPairMatcher extends
+ TypeSafeMatcher<Collection<Link>> {
+ private final String source;
+ private final String destination;
+
+ /**
+ * Creates a matcher for a given path represented by a source and
+ * a destination.
+ *
+ * @param source string identifier for the source of the path
+ * @param destination string identifier for the destination of the path
+ */
+ LinksHaveEntryWithSourceDestinationPairMatcher(String source,
+ String destination) {
+ this.source = source;
+ this.destination = destination;
+ }
+
+ @Override
+ public boolean matchesSafely(Collection<Link> links) {
+ for (Link link : links) {
+ if (source.equals(destination) && link instanceof EdgeLink) {
+ EdgeLink edgeLink = (EdgeLink) link;
+ if (edgeLink.hostLocation().elementId()
+ .toString().endsWith(source)) {
+ return true;
+ }
+ }
+
+ if (link.src().elementId().toString().endsWith(source) &&
+ link.dst().elementId().toString().endsWith(destination)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("link lookup for source \"");
+ description.appendText(source);
+ description.appendText(" and destination ");
+ description.appendText(destination);
+ description.appendText("\"");
+ }
+
+ @Override
+ public void describeMismatchSafely(Collection<Link> links,
+ Description mismatchDescription) {
+ mismatchDescription.appendText("was ").
+ appendText(links.toString());
+ }
+
+ /**
+ * Creates a link has path matcher.
+ *
+ * @param source string identifier for the source of the path
+ * @param destination string identifier for the destination of the path
+ * @return matcher to match the path
+ */
+ public static LinksHaveEntryWithSourceDestinationPairMatcher linksHasPath(
+ String source,
+ String destination) {
+ return new LinksHaveEntryWithSourceDestinationPairMatcher(source,
+ destination);
+ }
+}
+
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java
new file mode 100644
index 00000000..219d2fd5
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentAccumulatorTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentBatchDelegate;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.IntentTestsMocks.MockIntent;
+import org.onosproject.net.intent.IntentTestsMocks.MockTimestamp;
+import org.onosproject.net.intent.MockIdGenerator;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+
+/**
+ * Unit tests for the intent accumulator.
+ */
+public class IntentAccumulatorTest {
+
+ Intent intent1;
+ Intent intent2;
+ Intent intent3;
+ IdGenerator mockGenerator;
+
+ private static IntentDataMatcher containsIntent(Intent intent) {
+ return new IntentDataMatcher(intent);
+ }
+
+ /**
+ * Creates mock intents used by the test.
+ */
+ @Before
+ public void localSetup() {
+ mockGenerator = new MockIdGenerator();
+ Intent.bindIdGenerator(mockGenerator);
+
+ intent1 = new MockIntent(1L);
+ intent2 = new MockIntent(2L);
+ intent3 = new MockIntent(3L);
+ }
+
+ /**
+ * Removes id generator from the Intent class.
+ */
+ @After
+ public void localTearDown() {
+ Intent.unbindIdGenerator(mockGenerator);
+ }
+
+ /**
+ * Hamcrest matcher to check that a collection of intent data objects
+ * contains an entry for a given intent.
+ */
+ private static final class IntentDataMatcher
+ extends TypeSafeDiagnosingMatcher<Collection<IntentData>> {
+
+ final Intent intent;
+
+ public IntentDataMatcher(Intent intent) {
+ this.intent = intent;
+ }
+
+ /**
+ * Check that the given collection of intent data contains a specific
+ * intent.
+ *
+ * @param operations collection of intent data
+ * @param description description
+ * @return true if the collection contains the intent, false otherwise.
+ */
+ public boolean matchesSafely(Collection<IntentData> operations,
+ Description description) {
+ for (IntentData operation : operations) {
+ if (operation.key().equals(intent.key())) {
+ if (operation.state() != IntentState.INSTALLED) {
+ description.appendText("state was " + operation.state());
+ return false;
+ }
+ return true;
+ }
+ }
+ description.appendText("key was not found " + intent.key());
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("INSTALLED state intent with key " + intent.key());
+ }
+ }
+
+ /**
+ * Mock batch delegate class. Gets calls from the accumulator and checks
+ * that the operations have been properly compressed.
+ */
+ private class MockIntentBatchDelegate
+ implements IntentBatchDelegate {
+ public void execute(Collection<IntentData> operations) {
+ assertThat(operations, hasSize(3));
+ assertThat(operations, containsIntent(intent1));
+ assertThat(operations, containsIntent(intent2));
+ assertThat(operations, containsIntent(intent3));
+ }
+ }
+
+ /**
+ * Tests that the accumulator properly compresses operations on the same
+ * intents.
+ */
+ @Test
+ public void checkAccumulator() {
+
+ MockIntentBatchDelegate delegate = new MockIntentBatchDelegate();
+ IntentAccumulator accumulator = new IntentAccumulator(delegate);
+
+ List<IntentData> intentDataItems = ImmutableList.of(
+ new IntentData(intent1, IntentState.INSTALLING,
+ new MockTimestamp(1)),
+ new IntentData(intent2, IntentState.INSTALLING,
+ new MockTimestamp(1)),
+ new IntentData(intent3, IntentState.INSTALLED,
+ new MockTimestamp(1)),
+ new IntentData(intent2, IntentState.INSTALLED,
+ new MockTimestamp(1)),
+ new IntentData(intent2, IntentState.INSTALLED,
+ new MockTimestamp(1)),
+ new IntentData(intent1, IntentState.INSTALLED,
+ new MockTimestamp(1)));
+
+
+ accumulator.processItems(intentDataItems);
+ }
+
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java
new file mode 100644
index 00000000..0f6ce67c
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl;
+
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentServiceAdapter;
+import org.onosproject.net.intent.IntentStore;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.trivial.SimpleIntentStore;
+import org.onosproject.store.trivial.SystemClockTimestamp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.intent.IntentState.*;
+import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
+
+/**
+ * Test intent cleanup.
+ */
+public class IntentCleanupTest {
+
+ private IntentCleanup cleanup;
+ private MockIntentService service;
+ private IntentStore store;
+ protected IdGenerator idGenerator; // global or one per test? per test for now.
+
+ private static class MockIntentService extends IntentServiceAdapter {
+
+ private int submitCounter = 0;
+
+ @Override
+ public void submit(Intent intent) {
+ submitCounter++;
+ }
+
+ public int submitCounter() {
+ return submitCounter;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ service = new MockIntentService();
+ store = new SimpleIntentStore();
+ cleanup = new IntentCleanup();
+ idGenerator = new MockIdGenerator();
+
+ cleanup.cfgService = new ComponentConfigAdapter();
+ cleanup.service = service;
+ cleanup.store = store;
+ cleanup.period = 10;
+ cleanup.retryThreshold = 3;
+ cleanup.activate();
+
+ assertTrue("store should be empty",
+ Sets.newHashSet(cleanup.store.getIntents()).isEmpty());
+
+ Intent.bindIdGenerator(idGenerator);
+ }
+
+ @After
+ public void tearDown() {
+ cleanup.deactivate();
+
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Trigger resubmit of intent in CORRUPT during periodic poll.
+ */
+ @Test
+ public void corruptPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {}
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, version);
+ store.addPending(data);
+
+ cleanup.run(); //FIXME broken?
+ assertEquals("Expect number of submits incorrect",
+ 1, service.submitCounter());
+ }
+
+ /**
+ * Trigger resubmit of intent in INSTALL_REQ for too long.
+ */
+ @Test
+ public void pendingPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {}
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, version);
+ store.addPending(data);
+
+ cleanup.run();
+ assertEquals("Expect number of submits incorrect",
+ 1, service.submitCounter());
+
+ }
+
+ /**
+ * Trigger resubmit of intent in INSTALLING for too long.
+ */
+ @Test
+ public void installingPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(INSTALLING);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, version);
+ store.addPending(data);
+
+ cleanup.run();
+ assertEquals("Expect number of submits incorrect",
+ 1, service.submitCounter());
+
+ }
+
+ /**
+ * Only submit one of two intents because one is too new.
+ */
+ @Test
+ public void skipPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {}
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, null);
+ store.addPending(data);
+
+ Intent intent2 = new MockIntent(2L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ data = new IntentData(intent2, INSTALL_REQ, version);
+ store.addPending(data);
+
+ cleanup.run();
+ assertEquals("Expect number of submits incorrect",
+ 1, service.submitCounter());
+ }
+
+ /**
+ * Verify resubmit in response to CORRUPT event.
+ */
+ @Test
+ public void corruptEvent() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+
+ Intent intent = new MockIntent(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, null);
+
+ store.addPending(data);
+ assertEquals("Expect number of submits incorrect",
+ 1, service.submitCounter());
+ }
+
+ /**
+ * Intent should not be retried because threshold is reached.
+ */
+ @Test
+ public void corruptEventThreshold() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ intentData.setErrorCount(cleanup.retryThreshold);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, null);
+
+ store.addPending(data);
+ assertEquals("Expect number of submits incorrect",
+ 0, service.submitCounter());
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java
new file mode 100644
index 00000000..15ee24e6
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl;
+
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentStore;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.trivial.SimpleIntentStore;
+import org.onosproject.store.trivial.SystemClockTimestamp;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.intent.IntentState.*;
+import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
+
+/**
+ * Test intent cleanup using Mocks.
+ * FIXME remove this or IntentCleanupTest
+ */
+public class IntentCleanupTestMock {
+
+ private IntentCleanup cleanup;
+ private IntentService service;
+ private IntentStore store;
+ protected IdGenerator idGenerator; // global or one per test? per test for now.
+
+ @Before
+ public void setUp() {
+ service = createMock(IntentService.class);
+ store = new SimpleIntentStore();
+ cleanup = new IntentCleanup();
+ idGenerator = new MockIdGenerator();
+
+ service.addListener(cleanup);
+ expectLastCall().once();
+ replay(service);
+
+ cleanup.cfgService = new ComponentConfigAdapter();
+ cleanup.service = service;
+ cleanup.store = store;
+ cleanup.period = 1000;
+ cleanup.retryThreshold = 3;
+ cleanup.activate();
+
+ verify(service);
+ reset(service);
+
+ assertTrue("store should be empty",
+ Sets.newHashSet(cleanup.store.getIntents()).isEmpty());
+
+ Intent.bindIdGenerator(idGenerator);
+ }
+
+ @After
+ public void tearDown() {
+ service.removeListener(cleanup);
+ expectLastCall().once();
+ replay(service);
+
+ cleanup.deactivate();
+
+ verify(service);
+ reset(service);
+
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Trigger resubmit of intent in CORRUPT during periodic poll.
+ */
+ @Test
+ public void corruptPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {}
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, version);
+ store.addPending(data);
+
+ service.submit(intent);
+ expectLastCall().once();
+ replay(service);
+
+ cleanup.run(); //FIXME broken?
+ verify(service);
+ reset(service);
+ }
+
+ /**
+ * Trigger resubmit of intent in INSTALL_REQ for too long.
+ */
+ @Test
+ public void pendingPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {}
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, version);
+ store.addPending(data);
+
+ service.submit(intent);
+ expectLastCall().once();
+ replay(service);
+
+ cleanup.run();
+ verify(service);
+ reset(service);
+ }
+
+ /**
+ * Trigger resubmit of intent in INSTALLING for too long.
+ */
+ @Test
+ public void installingPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(INSTALLING);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, version);
+ store.addPending(data);
+
+ service.submit(intent);
+ expectLastCall().once();
+ replay(service);
+
+ cleanup.run();
+ verify(service);
+ reset(service);
+ }
+
+ /**
+ * Only submit one of two intents because one is too new.
+ */
+ @Test
+ public void skipPoll() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {}
+ };
+ store.setDelegate(mockDelegate);
+
+ Intent intent = new MockIntent(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, null);
+ store.addPending(data);
+
+ Intent intent2 = new MockIntent(2L);
+ Timestamp version = new SystemClockTimestamp(1L);
+ data = new IntentData(intent2, INSTALL_REQ, version);
+ store.addPending(data);
+
+ service.submit(intent2);
+ expectLastCall().once();
+ replay(service);
+
+ cleanup.run();
+ verify(service);
+ reset(service);
+ }
+
+ /**
+ * Verify resubmit in response to CORRUPT event.
+ */
+ @Test
+ public void corruptEvent() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+
+ Intent intent = new MockIntent(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, null);
+
+ service.submit(intent);
+ expectLastCall().once();
+ replay(service);
+
+ store.addPending(data);
+
+ verify(service);
+ reset(service);
+ }
+
+ /**
+ * Intent should not be retried because threshold is reached.
+ */
+ @Test
+ public void corruptEventThreshold() {
+ IntentStoreDelegate mockDelegate = new IntentStoreDelegate() {
+ @Override
+ public void process(IntentData intentData) {
+ intentData.setState(CORRUPT);
+ intentData.setErrorCount(cleanup.retryThreshold);
+ store.write(intentData);
+ }
+
+ @Override
+ public void notify(IntentEvent event) {
+ cleanup.event(event);
+ }
+ };
+ store.setDelegate(mockDelegate);
+
+
+ Intent intent = new MockIntent(1L);
+ IntentData data = new IntentData(intent, INSTALL_REQ, null);
+
+ replay(service);
+
+ store.addPending(data);
+
+ verify(service);
+ reset(service);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java
new file mode 100644
index 00000000..4bf32f43
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/IntentManagerTest.java
@@ -0,0 +1,672 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.impl.TestCoreManager;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentCompiler;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentEvent.Type;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.resource.link.LinkResourceAllocations;
+import org.onosproject.store.trivial.SimpleIntentStore;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+import static org.onlab.junit.TestTools.assertAfter;
+import static org.onlab.util.Tools.delay;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+import static org.onosproject.net.intent.IntentState.*;
+import static org.onosproject.net.intent.IntentTestsMocks.MockFlowRule;
+import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
+
+/**
+ * Test intent manager and transitions.
+ *
+ * TODO implement the following tests:
+ * - {submit, withdraw, update, replace} intent
+ * - {submit, update, recompiling} intent with failed compilation
+ * - failed reservation
+ * - push timeout recovery
+ * - failed items recovery
+ *
+ * in general, verify intents store, flow store, and work queue
+ */
+
+public class IntentManagerTest {
+
+ private static final int SUBMIT_TIMEOUT_MS = 1000;
+ private static final ApplicationId APPID = new TestApplicationId("manager-test");
+
+ private IntentManager manager;
+ private MockFlowRuleService flowRuleService;
+
+ protected IntentService service;
+ protected IntentExtensionService extensionService;
+ protected TestListener listener = new TestListener();
+ protected TestIntentCompiler compiler = new TestIntentCompiler();
+
+ private static class TestListener implements IntentListener {
+ final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create();
+ Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap();
+
+ @Override
+ public void event(IntentEvent event) {
+ events.put(event.type(), event);
+ if (latchMap.containsKey(event.type())) {
+ latchMap.get(event.type()).countDown();
+ }
+ }
+
+ public int getCounts(IntentEvent.Type type) {
+ return events.get(type).size();
+ }
+
+ public void setLatch(int count, IntentEvent.Type type) {
+ latchMap.put(type, new CountDownLatch(count));
+ }
+
+ public void await(IntentEvent.Type type) {
+ try {
+ assertTrue("Timed out waiting for: " + type,
+ latchMap.get(type).await(5, TimeUnit.SECONDS));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static class TestIntentTracker implements ObjectiveTrackerService {
+ private TopologyChangeDelegate delegate;
+ @Override
+ public void setDelegate(TopologyChangeDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(TopologyChangeDelegate delegate) {
+ if (delegate.equals(this.delegate)) {
+ this.delegate = null;
+ }
+ }
+
+ @Override
+ public void addTrackedResources(Key key, Collection<NetworkResource> resources) {
+ //TODO
+ }
+
+ @Override
+ public void removeTrackedResources(Key key, Collection<NetworkResource> resources) {
+ //TODO
+ }
+
+ @Override
+ public void trackIntent(IntentData intentData) {
+ //TODO
+ }
+ }
+
+ private static class MockInstallableIntent extends FlowRuleIntent {
+
+ public MockInstallableIntent() {
+ super(APPID, Collections.singletonList(new MockFlowRule(100)));
+ }
+ }
+
+ private static class TestIntentCompiler implements IntentCompiler<MockIntent> {
+ @Override
+ public List<Intent> compile(MockIntent intent, List<Intent> installable,
+ Set<LinkResourceAllocations> resources) {
+ return Lists.newArrayList(new MockInstallableIntent());
+ }
+ }
+
+ private static class TestIntentCompilerMultipleFlows implements IntentCompiler<MockIntent> {
+ @Override
+ public List<Intent> compile(MockIntent intent, List<Intent> installable,
+ Set<LinkResourceAllocations> resources) {
+
+ return IntStream.rangeClosed(1, 5)
+ .mapToObj(mock -> (new MockInstallableIntent()))
+ .collect(Collectors.toList());
+ }
+ }
+
+
+ private static class TestIntentCompilerError implements IntentCompiler<MockIntent> {
+ @Override
+ public List<Intent> compile(MockIntent intent, List<Intent> installable,
+ Set<LinkResourceAllocations> resources) {
+ throw new IntentCompilationException("Compilation always fails");
+ }
+ }
+
+ /**
+ * Hamcrest matcher to check that a collection of Intents contains an
+ * Intent with the specified Intent Id.
+ */
+ public static class EntryForIntentMatcher extends TypeSafeMatcher<Collection<Intent>> {
+ private final IntentId id;
+
+ public EntryForIntentMatcher(IntentId idValue) {
+ id = idValue;
+ }
+
+ @Override
+ public boolean matchesSafely(Collection<Intent> intents) {
+ for (Intent intent : intents) {
+ if (intent.id().equals(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("an intent with id \" ").
+ appendText(id.toString()).
+ appendText("\"");
+ }
+ }
+
+ private static EntryForIntentMatcher hasIntentWithId(IntentId id) {
+ return new EntryForIntentMatcher(id);
+ }
+
+ @Before
+ public void setUp() {
+ manager = new IntentManager();
+ flowRuleService = new MockFlowRuleService();
+ manager.store = new SimpleIntentStore();
+ injectEventDispatcher(manager, new TestEventDispatcher());
+ manager.trackerService = new TestIntentTracker();
+ manager.flowRuleService = flowRuleService;
+ manager.coreService = new TestCoreManager();
+ service = manager;
+ extensionService = manager;
+
+ manager.activate();
+ service.addListener(listener);
+ extensionService.registerCompiler(MockIntent.class, compiler);
+
+ assertTrue("store should be empty",
+ Sets.newHashSet(service.getIntents()).isEmpty());
+ assertEquals(0L, flowRuleService.getFlowRuleCount());
+ }
+
+ public void verifyState() {
+ // verify that all intents are parked and the batch operation is unblocked
+ Set<IntentState> parked = Sets.newHashSet(INSTALLED, WITHDRAWN, FAILED, CORRUPT);
+ for (Intent i : service.getIntents()) {
+ IntentState state = service.getIntentState(i.key());
+ assertTrue("Intent " + i.id() + " is in invalid state " + state,
+ parked.contains(state));
+ }
+ //the batch has not yet been removed when we receive the last event
+ // FIXME: this doesn't guarantee to avoid the race
+
+ //FIXME
+// for (int tries = 0; tries < 10; tries++) {
+// if (manager.batchService.getPendingOperations().isEmpty()) {
+// break;
+// }
+// delay(10);
+// }
+// assertTrue("There are still pending batch operations.",
+// manager.batchService.getPendingOperations().isEmpty());
+
+ }
+
+ @After
+ public void tearDown() {
+ extensionService.unregisterCompiler(MockIntent.class);
+ service.removeListener(listener);
+ manager.deactivate();
+ // TODO null the other refs?
+ }
+
+ @Test
+ public void submitIntent() {
+ flowRuleService.setFuture(true);
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.INSTALLED);
+ Intent intent = new MockIntent(MockIntent.nextId());
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.INSTALLED);
+ assertEquals(1L, service.getIntentCount());
+ assertEquals(1L, flowRuleService.getFlowRuleCount());
+ verifyState();
+ }
+
+ @Test
+ public void withdrawIntent() {
+ flowRuleService.setFuture(true);
+
+ listener.setLatch(1, Type.INSTALLED);
+ Intent intent = new MockIntent(MockIntent.nextId());
+ service.submit(intent);
+ listener.await(Type.INSTALLED);
+ assertEquals(1L, service.getIntentCount());
+ assertEquals(1L, flowRuleService.getFlowRuleCount());
+
+ listener.setLatch(1, Type.WITHDRAWN);
+ service.withdraw(intent);
+ listener.await(Type.WITHDRAWN);
+ assertEquals(0L, flowRuleService.getFlowRuleCount());
+ verifyState();
+ }
+
+ @Test
+ @Ignore("This is disabled because we are seeing intermittent failures on Jenkins")
+ public void stressSubmitWithdrawUnique() {
+ flowRuleService.setFuture(true);
+
+ int count = 500;
+ Intent[] intents = new Intent[count];
+
+ listener.setLatch(count, Type.WITHDRAWN);
+
+ for (int i = 0; i < count; i++) {
+ intents[i] = new MockIntent(MockIntent.nextId());
+ service.submit(intents[i]);
+ }
+
+ for (int i = 0; i < count; i++) {
+ service.withdraw(intents[i]);
+ }
+
+ listener.await(Type.WITHDRAWN);
+ assertEquals(0L, flowRuleService.getFlowRuleCount());
+ verifyState();
+ }
+
+ @Test
+ public void stressSubmitWithdrawSame() {
+ flowRuleService.setFuture(true);
+
+ int count = 50;
+
+ Intent intent = new MockIntent(MockIntent.nextId());
+ for (int i = 0; i < count; i++) {
+ service.submit(intent);
+ service.withdraw(intent);
+ }
+
+ assertAfter(SUBMIT_TIMEOUT_MS, () -> {
+ assertEquals(1L, service.getIntentCount());
+ assertEquals(0L, flowRuleService.getFlowRuleCount());
+ });
+ verifyState();
+ }
+
+
+ /**
+ * Tests for proper behavior of installation of an intent that triggers
+ * a compilation error.
+ */
+ @Test
+ public void errorIntentCompile() {
+ final TestIntentCompilerError errorCompiler = new TestIntentCompilerError();
+ extensionService.registerCompiler(MockIntent.class, errorCompiler);
+ MockIntent intent = new MockIntent(MockIntent.nextId());
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.FAILED);
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.FAILED);
+ verifyState();
+ }
+
+ /**
+ * Tests handling a future that contains an error as a result of
+ * installing an intent.
+ */
+ @Ignore("skipping until we fix update ordering problem")
+ @Test
+ public void errorIntentInstallFromFlows() {
+ final Long id = MockIntent.nextId();
+ flowRuleService.setFuture(false);
+ MockIntent intent = new MockIntent(id);
+ listener.setLatch(1, Type.FAILED);
+ listener.setLatch(1, Type.INSTALL_REQ);
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.FAILED);
+ // FIXME the intent will be moved into INSTALLED immediately which overrides FAILED
+ // ... the updates come out of order
+ verifyState();
+ }
+
+ /**
+ * Tests handling a future that contains an unresolvable error as a result of
+ * installing an intent.
+ */
+ @Test
+ public void errorIntentInstallNeverTrue() {
+ final Long id = MockIntent.nextId();
+ flowRuleService.setFuture(false);
+ MockIntent intent = new MockIntent(id);
+ listener.setLatch(1, Type.CORRUPT);
+ listener.setLatch(1, Type.INSTALL_REQ);
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ // The delay here forces the retry loop in the intent manager to time out
+ delay(100);
+ flowRuleService.setFuture(false);
+ service.withdraw(intent);
+ listener.await(Type.CORRUPT);
+ verifyState();
+ }
+
+ /**
+ * Tests that a compiler for a subclass of an intent that already has a
+ * compiler is automatically added.
+ */
+ @Test
+ public void intentSubclassCompile() {
+ class MockIntentSubclass extends MockIntent {
+ public MockIntentSubclass(Long number) {
+ super(number);
+ }
+ }
+ flowRuleService.setFuture(true);
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.INSTALLED);
+ Intent intent = new MockIntentSubclass(MockIntent.nextId());
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.INSTALLED);
+ assertEquals(1L, service.getIntentCount());
+ assertEquals(1L, flowRuleService.getFlowRuleCount());
+
+ final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers =
+ extensionService.getCompilers();
+ assertEquals(2, compilers.size());
+ assertNotNull(compilers.get(MockIntentSubclass.class));
+ assertNotNull(compilers.get(MockIntent.class));
+ verifyState();
+ }
+
+ /**
+ * Tests an intent with no compiler.
+ */
+ @Test
+ public void intentWithoutCompiler() {
+ class IntentNoCompiler extends Intent {
+ IntentNoCompiler() {
+ super(APPID, null, Collections.emptyList(),
+ Intent.DEFAULT_INTENT_PRIORITY);
+ }
+ }
+
+ Intent intent = new IntentNoCompiler();
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.FAILED);
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.FAILED);
+ verifyState();
+ }
+
+ /**
+ * Tests an intent with no installer.
+ */
+ @Test
+ public void intentWithoutInstaller() {
+ MockIntent intent = new MockIntent(MockIntent.nextId());
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.CORRUPT);
+ service.submit(intent);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.CORRUPT);
+ verifyState();
+ }
+
+ /**
+ * Tests that the intent fetching methods are correct.
+ */
+ @Test
+ public void testIntentFetching() {
+ List<Intent> intents;
+
+ flowRuleService.setFuture(true);
+
+ intents = Lists.newArrayList(service.getIntents());
+ assertThat(intents, hasSize(0));
+
+ final MockIntent intent1 = new MockIntent(MockIntent.nextId());
+ final MockIntent intent2 = new MockIntent(MockIntent.nextId());
+
+ listener.setLatch(2, Type.INSTALL_REQ);
+ listener.setLatch(2, Type.INSTALLED);
+ service.submit(intent1);
+ service.submit(intent2);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.INSTALLED);
+ listener.await(Type.INSTALLED);
+
+ intents = Lists.newArrayList(service.getIntents());
+ assertThat(intents, hasSize(2));
+
+ assertThat(intents, hasIntentWithId(intent1.id()));
+ assertThat(intents, hasIntentWithId(intent2.id()));
+ verifyState();
+ }
+
+ /**
+ * Tests that removing all intents results in no flows remaining.
+ */
+ @Test
+ public void testFlowRemoval() {
+ List<Intent> intents;
+
+ flowRuleService.setFuture(true);
+
+ intents = Lists.newArrayList(service.getIntents());
+ assertThat(intents, hasSize(0));
+
+ final MockIntent intent1 = new MockIntent(MockIntent.nextId());
+ final MockIntent intent2 = new MockIntent(MockIntent.nextId());
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.INSTALLED);
+
+ service.submit(intent1);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.INSTALLED);
+
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.INSTALLED);
+
+ service.submit(intent2);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.INSTALLED);
+
+ assertThat(listener.getCounts(Type.INSTALLED), is(2));
+ assertThat(flowRuleService.getFlowRuleCount(), is(2));
+
+ listener.setLatch(1, Type.WITHDRAWN);
+ service.withdraw(intent1);
+ listener.await(Type.WITHDRAWN);
+
+ listener.setLatch(1, Type.WITHDRAWN);
+ service.withdraw(intent2);
+ listener.await(Type.WITHDRAWN);
+
+ assertThat(listener.getCounts(Type.WITHDRAWN), is(2));
+ assertThat(flowRuleService.getFlowRuleCount(), is(0));
+ }
+
+ /**
+ * Test failure to install an intent, then succeed on retry via IntentCleanup.
+ */
+ @Test
+ public void testCorruptCleanup() {
+ IntentCleanup cleanup = new IntentCleanup();
+ cleanup.service = manager;
+ cleanup.store = manager.store;
+ cleanup.cfgService = new ComponentConfigAdapter();
+
+ try {
+ cleanup.activate();
+
+ final TestIntentCompilerMultipleFlows errorCompiler = new TestIntentCompilerMultipleFlows();
+ extensionService.registerCompiler(MockIntent.class, errorCompiler);
+ List<Intent> intents;
+
+ flowRuleService.setFuture(false);
+
+ intents = Lists.newArrayList(service.getIntents());
+ assertThat(intents, hasSize(0));
+
+ final MockIntent intent1 = new MockIntent(MockIntent.nextId());
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.CORRUPT);
+ listener.setLatch(1, Type.INSTALLED);
+
+ service.submit(intent1);
+
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.CORRUPT);
+
+ flowRuleService.setFuture(true);
+
+ listener.await(Type.INSTALLED);
+
+ assertThat(listener.getCounts(Type.CORRUPT), is(1));
+ assertThat(listener.getCounts(Type.INSTALLED), is(1));
+ assertEquals(INSTALLED, manager.getIntentState(intent1.key()));
+ assertThat(flowRuleService.getFlowRuleCount(), is(5));
+ } finally {
+ cleanup.deactivate();
+ }
+ }
+
+ /**
+ * Test failure to install an intent, and verify retries.
+ */
+ @Test
+ public void testCorruptRetry() {
+ IntentCleanup cleanup = new IntentCleanup();
+ cleanup.service = manager;
+ cleanup.store = manager.store;
+ cleanup.cfgService = new ComponentConfigAdapter();
+ cleanup.period = 1_000_000;
+ cleanup.retryThreshold = 3;
+
+ try {
+ cleanup.activate();
+
+ final TestIntentCompilerMultipleFlows errorCompiler = new TestIntentCompilerMultipleFlows();
+ extensionService.registerCompiler(MockIntent.class, errorCompiler);
+ List<Intent> intents;
+
+ flowRuleService.setFuture(false);
+
+ intents = Lists.newArrayList(service.getIntents());
+ assertThat(intents, hasSize(0));
+
+ final MockIntent intent1 = new MockIntent(MockIntent.nextId());
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(cleanup.retryThreshold, Type.CORRUPT);
+ listener.setLatch(1, Type.INSTALLED);
+
+ service.submit(intent1);
+
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.CORRUPT);
+ assertEquals(CORRUPT, manager.getIntentState(intent1.key()));
+ assertThat(listener.getCounts(Type.CORRUPT), is(cleanup.retryThreshold));
+
+ } finally {
+ cleanup.deactivate();
+ }
+ }
+
+ /**
+ * Tests that an intent that fails installation results in no flows remaining.
+ */
+ @Test
+ @Ignore("MockFlowRule numbering issue") //test works if run independently
+ public void testFlowRemovalInstallError() {
+ final TestIntentCompilerMultipleFlows errorCompiler = new TestIntentCompilerMultipleFlows();
+ extensionService.registerCompiler(MockIntent.class, errorCompiler);
+ List<Intent> intents;
+
+ flowRuleService.setFuture(true);
+ //FIXME relying on "3" is brittle
+ flowRuleService.setErrorFlow(3);
+
+ intents = Lists.newArrayList(service.getIntents());
+ assertThat(intents, hasSize(0));
+
+ final MockIntent intent1 = new MockIntent(MockIntent.nextId());
+
+ listener.setLatch(1, Type.INSTALL_REQ);
+ listener.setLatch(1, Type.CORRUPT);
+
+ service.submit(intent1);
+ listener.await(Type.INSTALL_REQ);
+ listener.await(Type.CORRUPT);
+
+ assertThat(listener.getCounts(Type.CORRUPT), is(1));
+ // in this test, there will still be flows abandoned on the data plane
+ //assertThat(flowRuleService.getFlowRuleCount(), is(0));
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java
new file mode 100644
index 00000000..8bd29bf8
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/MockFlowRuleService.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl;
+
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleServiceAdapter;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.flow.FlowRuleOperation.Type.REMOVE;
+
+
+public class MockFlowRuleService extends FlowRuleServiceAdapter {
+
+ final Set<FlowRule> flows = Sets.newHashSet();
+ boolean success;
+
+ int errorFlow = -1;
+ public void setErrorFlow(int errorFlow) {
+ this.errorFlow = errorFlow;
+ }
+
+ public void setFuture(boolean success) {
+ this.success = success;
+ }
+
+ @Override
+ public void apply(FlowRuleOperations ops) {
+ AtomicBoolean thisSuccess = new AtomicBoolean(success);
+ ops.stages().forEach(stage -> stage.forEach(flow -> {
+ if (errorFlow == flow.rule().id().value()) {
+ thisSuccess.set(false);
+ } else {
+ switch (flow.type()) {
+ case ADD:
+ case MODIFY: //TODO is this the right behavior for modify?
+ flows.add(flow.rule());
+ break;
+ case REMOVE:
+ flows.remove(flow.rule());
+ break;
+ default:
+ break;
+ }
+ }
+ }));
+ if (thisSuccess.get()) {
+ ops.callback().onSuccess(ops);
+ } else {
+ ops.callback().onError(ops);
+ }
+ }
+
+ @Override
+ public int getFlowRuleCount() {
+ return flows.size();
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ return flows.stream()
+ .filter(flow -> flow.deviceId().equals(deviceId))
+ .map(DefaultFlowEntry::new)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void applyFlowRules(FlowRule... flowRules) {
+ for (FlowRule flow : flowRules) {
+ flows.add(flow);
+ }
+ }
+
+ @Override
+ public void removeFlowRules(FlowRule... flowRules) {
+ for (FlowRule flow : flowRules) {
+ flows.remove(flow);
+ }
+ }
+
+ @Override
+ public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
+ return flows.stream()
+ .filter(flow -> flow.appId() == id.id())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) {
+ return flows.stream()
+ .filter(flow -> flow.appId() == appId.id() && flow.groupId().id() == groupId)
+ .collect(Collectors.toList());
+ }
+}
+
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java
new file mode 100644
index 00000000..58fa1292
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/ObjectiveTrackerTest.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.junit.TestUtils.TestUtilsException;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.event.Event;
+import org.onosproject.net.Device;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.resource.link.LinkResourceEvent;
+import org.onosproject.net.resource.link.LinkResourceListener;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyListener;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import static org.easymock.EasyMock.createMock;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.device;
+import static org.onosproject.net.NetTestTools.link;
+
+/**
+ * Tests for the objective tracker.
+ */
+public class ObjectiveTrackerTest {
+ private static final int WAIT_TIMEOUT_SECONDS = 2;
+ private Topology topology;
+ private ObjectiveTracker tracker;
+ private TestTopologyChangeDelegate delegate;
+ private List<Event> reasons;
+ private TopologyListener listener;
+ private DeviceListener deviceListener;
+ private LinkResourceListener linkResourceListener;
+ private IdGenerator mockGenerator;
+
+ /**
+ * Initialization shared by all test cases.
+ *
+ * @throws TestUtilsException if any filed look ups fail
+ */
+ @Before
+ public void setUp() throws TestUtilsException {
+ topology = createMock(Topology.class);
+ tracker = new ObjectiveTracker();
+ delegate = new TestTopologyChangeDelegate();
+ tracker.setDelegate(delegate);
+ reasons = new LinkedList<>();
+ listener = TestUtils.getField(tracker, "listener");
+ deviceListener = TestUtils.getField(tracker, "deviceListener");
+ linkResourceListener = TestUtils.getField(tracker, "linkResourceListener");
+ mockGenerator = new MockIdGenerator();
+ Intent.bindIdGenerator(mockGenerator);
+ }
+
+ /**
+ * Code to clean up shared by all test case.
+ */
+ @After
+ public void tearDown() {
+ tracker.unsetDelegate(delegate);
+ Intent.unbindIdGenerator(mockGenerator);
+ }
+
+ /**
+ * Topology change delegate mock that tracks the events coming into it
+ * and saves them. It provides a latch so that tests can wait for events
+ * to be generated.
+ */
+ static class TestTopologyChangeDelegate implements TopologyChangeDelegate {
+
+ CountDownLatch latch = new CountDownLatch(1);
+ List<Key> intentIdsFromEvent;
+ boolean compileAllFailedFromEvent;
+
+ @Override
+ public void triggerCompile(Iterable<Key> intentKeys,
+ boolean compileAllFailed) {
+ intentIdsFromEvent = Lists.newArrayList(intentKeys);
+ compileAllFailedFromEvent = compileAllFailed;
+ latch.countDown();
+ }
+ }
+
+ /**
+ * Tests an event with no associated reasons.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+ @Test
+ public void testEventNoReasons() throws InterruptedException {
+ final TopologyEvent event = new TopologyEvent(
+ TopologyEvent.Type.TOPOLOGY_CHANGED,
+ topology,
+ null);
+
+ listener.event(event);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(0));
+ assertThat(delegate.compileAllFailedFromEvent, is(true));
+ }
+
+ /**
+ * Tests an event for a link down where none of the reasons match
+ * currently installed intents.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+ @Test
+ public void testEventLinkDownNoMatches() throws InterruptedException {
+ final Link link = link("src", 1, "dst", 2);
+ final LinkEvent linkEvent = new LinkEvent(LinkEvent.Type.LINK_REMOVED, link);
+ reasons.add(linkEvent);
+
+ final TopologyEvent event = new TopologyEvent(
+ TopologyEvent.Type.TOPOLOGY_CHANGED,
+ topology,
+ reasons);
+
+ listener.event(event);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(0));
+ assertThat(delegate.compileAllFailedFromEvent, is(false));
+ }
+
+ /**
+ * Tests an event for a link being added.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+ @Test
+ public void testEventLinkAdded() throws InterruptedException {
+ final Link link = link("src", 1, "dst", 2);
+ final LinkEvent linkEvent = new LinkEvent(LinkEvent.Type.LINK_ADDED, link);
+ reasons.add(linkEvent);
+
+ final TopologyEvent event = new TopologyEvent(
+ TopologyEvent.Type.TOPOLOGY_CHANGED,
+ topology,
+ reasons);
+
+ listener.event(event);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(0));
+ assertThat(delegate.compileAllFailedFromEvent, is(true));
+ }
+
+ /**
+ * Tests an event for a link down where the link matches existing intents.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+ @Test
+ public void testEventLinkDownMatch() throws Exception {
+ final Link link = link("src", 1, "dst", 2);
+ final LinkEvent linkEvent = new LinkEvent(LinkEvent.Type.LINK_REMOVED, link);
+ reasons.add(linkEvent);
+
+ final TopologyEvent event = new TopologyEvent(
+ TopologyEvent.Type.TOPOLOGY_CHANGED,
+ topology,
+ reasons);
+
+ final Key key = Key.of(0x333L, APP_ID);
+ Collection<NetworkResource> resources = ImmutableSet.of(link);
+ tracker.addTrackedResources(key, resources);
+
+ listener.event(event);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(1));
+ assertThat(delegate.compileAllFailedFromEvent, is(false));
+ assertThat(delegate.intentIdsFromEvent.get(0).toString(),
+ equalTo("0x333"));
+ }
+
+ /**
+ * Tests a resource available event.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+ @Test
+ public void testResourceEvent() throws Exception {
+ LinkResourceEvent event = new LinkResourceEvent(
+ LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE,
+ new HashSet<>());
+ linkResourceListener.event(event);
+
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(0));
+ assertThat(delegate.compileAllFailedFromEvent, is(true));
+ }
+
+ /**
+ * Tests an event for a host becoming available that matches an intent.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+
+ @Test
+ public void testEventHostAvailableMatch() throws Exception {
+ final Device host = device("host1");
+
+ final DeviceEvent deviceEvent =
+ new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, host);
+ reasons.add(deviceEvent);
+
+ final Key key = Key.of(0x333L, APP_ID);
+ Collection<NetworkResource> resources = ImmutableSet.of(host.id());
+ tracker.addTrackedResources(key, resources);
+
+ deviceListener.event(deviceEvent);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(1));
+ assertThat(delegate.compileAllFailedFromEvent, is(true));
+ assertThat(delegate.intentIdsFromEvent.get(0).toString(),
+ equalTo("0x333"));
+ }
+
+ /**
+ * Tests an event for a host becoming unavailable that matches an intent.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+
+ @Test
+ public void testEventHostUnavailableMatch() throws Exception {
+ final Device host = device("host1");
+
+ final DeviceEvent deviceEvent =
+ new DeviceEvent(DeviceEvent.Type.DEVICE_REMOVED, host);
+ reasons.add(deviceEvent);
+
+ final Key key = Key.of(0x333L, APP_ID);
+ Collection<NetworkResource> resources = ImmutableSet.of(host.id());
+ tracker.addTrackedResources(key, resources);
+
+ deviceListener.event(deviceEvent);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(1));
+ assertThat(delegate.compileAllFailedFromEvent, is(false));
+ assertThat(delegate.intentIdsFromEvent.get(0).toString(),
+ equalTo("0x333"));
+ }
+
+ /**
+ * Tests an event for a host becoming available that matches an intent.
+ *
+ * @throws InterruptedException if the latch wait fails.
+ */
+
+ @Test
+ public void testEventHostAvailableNoMatch() throws Exception {
+ final Device host = device("host1");
+
+ final DeviceEvent deviceEvent =
+ new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, host);
+ reasons.add(deviceEvent);
+
+ deviceListener.event(deviceEvent);
+ assertThat(
+ delegate.latch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS),
+ is(true));
+
+ assertThat(delegate.intentIdsFromEvent, hasSize(0));
+ assertThat(delegate.compileAllFailedFromEvent, is(true));
+ }
+
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java
new file mode 100644
index 00000000..5588904d
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompilerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.PathIntent;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import java.util.List;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.NetTestTools.hid;
+import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
+
+/**
+ * Unit tests for the HostToHost intent compiler.
+ */
+public class HostToHostIntentCompilerTest extends AbstractIntentTest {
+ private static final String HOST_ONE_MAC = "00:00:00:00:00:01";
+ private static final String HOST_TWO_MAC = "00:00:00:00:00:02";
+ private static final String HOST_ONE_VLAN = "-1";
+ private static final String HOST_TWO_VLAN = "-1";
+ private static final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN;
+ private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN;
+
+ private static final ApplicationId APPID = new TestApplicationId("foo");
+
+ private TrafficSelector selector = new IntentTestsMocks.MockSelector();
+ private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
+
+ private HostId hostOneId = HostId.hostId(HOST_ONE);
+ private HostId hostTwoId = HostId.hostId(HOST_TWO);
+ private HostService mockHostService;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ Host hostOne = createMock(Host.class);
+ expect(hostOne.mac()).andReturn(new MacAddress(HOST_ONE_MAC.getBytes())).anyTimes();
+ expect(hostOne.vlan()).andReturn(VlanId.vlanId()).anyTimes();
+ replay(hostOne);
+
+ Host hostTwo = createMock(Host.class);
+ expect(hostTwo.mac()).andReturn(new MacAddress(HOST_TWO_MAC.getBytes())).anyTimes();
+ expect(hostTwo.vlan()).andReturn(VlanId.vlanId()).anyTimes();
+ replay(hostTwo);
+
+ mockHostService = createMock(HostService.class);
+ expect(mockHostService.getHost(eq(hostOneId))).andReturn(hostOne).anyTimes();
+ expect(mockHostService.getHost(eq(hostTwoId))).andReturn(hostTwo).anyTimes();
+ replay(mockHostService);
+ }
+
+ /**
+ * Creates a HostToHost intent based on two host Ids.
+ *
+ * @param oneIdString string for host one id
+ * @param twoIdString string for host two id
+ * @return HostToHostIntent for the two hosts
+ */
+ private HostToHostIntent makeIntent(String oneIdString, String twoIdString) {
+ return HostToHostIntent.builder()
+ .appId(APPID)
+ .one(hid(oneIdString))
+ .two(hid(twoIdString))
+ .selector(selector)
+ .treatment(treatment)
+ .build();
+ }
+
+ /**
+ * Creates a compiler for HostToHost intents.
+ *
+ * @param hops string array describing the path hops to use when compiling
+ * @return HostToHost intent compiler
+ */
+ private HostToHostIntentCompiler makeCompiler(String[] hops) {
+ HostToHostIntentCompiler compiler =
+ new HostToHostIntentCompiler();
+ compiler.pathService = new IntentTestsMocks.MockPathService(hops);
+ compiler.hostService = mockHostService;
+ return compiler;
+ }
+
+
+ /**
+ * Tests a pair of hosts with 8 hops between them.
+ */
+ @Test
+ public void testSingleLongPathCompilation() {
+
+ HostToHostIntent intent = makeIntent(HOST_ONE,
+ HOST_TWO);
+ assertThat(intent, is(notNullValue()));
+
+ String[] hops = {HOST_ONE, "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", HOST_TWO};
+ HostToHostIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(Matchers.notNullValue()));
+ assertThat(result, hasSize(2));
+ Intent forwardResultIntent = result.get(0);
+ assertThat(forwardResultIntent instanceof PathIntent, is(true));
+ Intent reverseResultIntent = result.get(1);
+ assertThat(reverseResultIntent instanceof PathIntent, is(true));
+
+ if (forwardResultIntent instanceof PathIntent) {
+ PathIntent forwardPathIntent = (PathIntent) forwardResultIntent;
+ assertThat(forwardPathIntent.path().links(), hasSize(9));
+ assertThat(forwardPathIntent.path().links(), linksHasPath(HOST_ONE, "h1"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h1", "h2"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h2", "h3"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h3", "h4"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h4", "h5"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h5", "h6"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h6", "h7"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h7", "h8"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("h8", HOST_TWO));
+ }
+
+ if (reverseResultIntent instanceof PathIntent) {
+ PathIntent reversePathIntent = (PathIntent) reverseResultIntent;
+ assertThat(reversePathIntent.path().links(), hasSize(9));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h1", HOST_ONE));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h2", "h1"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h3", "h2"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h4", "h3"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h5", "h4"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h6", "h5"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h7", "h6"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("h8", "h7"));
+ assertThat(reversePathIntent.path().links(), linksHasPath(HOST_TWO, "h8"));
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
new file mode 100644
index 00000000..c5fa3719
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.MockIdGenerator;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.PID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+
+public class LinkCollectionIntentCompilerTest {
+
+ private final ApplicationId appId = new TestApplicationId("test");
+
+ private final ConnectPoint d1p1 = connectPoint("s1", 0);
+ private final ConnectPoint d2p0 = connectPoint("s2", 0);
+ private final ConnectPoint d2p1 = connectPoint("s2", 1);
+ private final ConnectPoint d3p1 = connectPoint("s3", 1);
+ private final ConnectPoint d3p0 = connectPoint("s3", 10);
+ private final ConnectPoint d1p0 = connectPoint("s1", 10);
+
+ private final Set<Link> links = ImmutableSet.of(
+ new DefaultLink(PID, d1p1, d2p0, DIRECT),
+ new DefaultLink(PID, d2p1, d3p1, DIRECT),
+ new DefaultLink(PID, d1p1, d3p1, DIRECT));
+
+ private final TrafficSelector selector = DefaultTrafficSelector.builder().build();
+ private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ private CoreService coreService;
+ private IntentExtensionService intentExtensionService;
+ private IdGenerator idGenerator = new MockIdGenerator();
+
+ private LinkCollectionIntent intent;
+
+ private LinkCollectionIntentCompiler sut;
+
+ @Before
+ public void setUp() {
+ sut = new LinkCollectionIntentCompiler();
+ coreService = createMock(CoreService.class);
+ expect(coreService.registerApplication("org.onosproject.net.intent"))
+ .andReturn(appId);
+ sut.coreService = coreService;
+
+ Intent.bindIdGenerator(idGenerator);
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links)
+ .ingressPoints(ImmutableSet.of(d1p1))
+ .egressPoints(ImmutableSet.of(d3p1))
+ .build();
+ intentExtensionService = createMock(IntentExtensionService.class);
+ intentExtensionService.registerCompiler(LinkCollectionIntent.class, sut);
+ intentExtensionService.unregisterCompiler(LinkCollectionIntent.class);
+ sut.intentManager = intentExtensionService;
+
+ replay(coreService, intentExtensionService);
+ }
+
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ @Test
+ public void testCompile() {
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(links.size()));
+
+ // if not found, get() raises an exception
+ FlowRule rule1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule1.selector(), is(
+ DefaultTrafficSelector.builder(intent.selector()).matchInPort(d1p1.port()).build()
+ ));
+ assertThat(rule1.treatment(), is(
+ DefaultTrafficTreatment.builder(intent.treatment()).setOutput(d1p1.port()).build()
+ ));
+ assertThat(rule1.priority(), is(intent.priority()));
+
+ FlowRule rule2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule2.selector(), is(
+ DefaultTrafficSelector.builder(intent.selector()).matchInPort(d2p0.port()).build()
+ ));
+ assertThat(rule2.treatment(), is(
+ DefaultTrafficTreatment.builder().setOutput(d2p1.port()).build()
+ ));
+ assertThat(rule2.priority(), is(intent.priority()));
+
+ FlowRule rule3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule3.selector(), is(
+ DefaultTrafficSelector.builder(intent.selector()).matchInPort(d3p1.port()).build()
+ ));
+ assertThat(rule3.treatment(), is(
+ DefaultTrafficTreatment.builder().setOutput(d3p1.port()).build()
+ ));
+ assertThat(rule3.priority(), is(intent.priority()));
+
+ sut.deactivate();
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java
new file mode 100644
index 00000000..76b26f46
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompilerTest.java
@@ -0,0 +1,188 @@
+package org.onosproject.net.intent.impl.compiler;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+
+import org.onlab.packet.MplsLabel;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.MplsIntent;
+import org.onosproject.net.intent.MplsPathIntent;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
+
+/**
+ * Unit tests for the HostToHost intent compiler.
+ */
+public class MplsIntentCompilerTest extends AbstractIntentTest {
+
+ private static final ApplicationId APPID = new TestApplicationId("foo");
+
+ private TrafficSelector selector = new IntentTestsMocks.MockSelector();
+ private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
+
+ /**
+ * Creates a PointToPoint intent based on ingress and egress device Ids.
+ *
+ * @param ingressIdString string for id of ingress device
+ * @param egressIdString string for id of egress device
+ * @return PointToPointIntent for the two devices
+ */
+ private MplsIntent makeIntent(String ingressIdString, Optional<MplsLabel> ingressLabel,
+ String egressIdString, Optional<MplsLabel> egressLabel) {
+
+ return MplsIntent.builder()
+ .appId(APPID)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(connectPoint(ingressIdString, 1))
+ .ingressLabel(ingressLabel)
+ .egressPoint(connectPoint(egressIdString, 1))
+ .egressLabel(egressLabel).build();
+ }
+ /**
+ * Creates a compiler for HostToHost intents.
+ *
+ * @param hops string array describing the path hops to use when compiling
+ * @return HostToHost intent compiler
+ */
+ private MplsIntentCompiler makeCompiler(String[] hops) {
+ MplsIntentCompiler compiler =
+ new MplsIntentCompiler();
+ compiler.pathService = new IntentTestsMocks.MockPathService(hops);
+ return compiler;
+ }
+
+
+ /**
+ * Tests a pair of devices in an 8 hop path, forward direction.
+ */
+ @Test
+ public void testForwardPathCompilation() {
+ Optional<MplsLabel> ingressLabel = Optional.of(MplsLabel.mplsLabel(10));
+ Optional<MplsLabel> egressLabel = Optional.of(MplsLabel.mplsLabel(20));
+
+ MplsIntent intent = makeIntent("d1", ingressLabel, "d8", egressLabel);
+ assertThat(intent, is(notNullValue()));
+
+ String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"};
+ MplsIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(Matchers.notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent forwardResultIntent = result.get(0);
+ assertThat(forwardResultIntent instanceof MplsPathIntent, is(true));
+
+ // if statement suppresses static analysis warnings about unchecked cast
+ if (forwardResultIntent instanceof MplsPathIntent) {
+ MplsPathIntent forwardPathIntent = (MplsPathIntent) forwardResultIntent;
+ // 7 links for the hops, plus one default lnk on ingress and egress
+ assertThat(forwardPathIntent.path().links(), hasSize(hops.length + 1));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d1", "d2"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d2", "d3"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d3", "d4"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d4", "d5"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d5", "d6"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d6", "d7"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d7", "d8"));
+ assertEquals(forwardPathIntent.egressLabel(), egressLabel);
+ assertEquals(forwardPathIntent.ingressLabel(), ingressLabel);
+ }
+ }
+
+ /**
+ * Tests a pair of devices in an 8 hop path, forward direction.
+ */
+ @Test
+ public void testReversePathCompilation() {
+ Optional<MplsLabel> ingressLabel = Optional.of(MplsLabel.mplsLabel(10));
+ Optional<MplsLabel> egressLabel = Optional.of(MplsLabel.mplsLabel(20));
+
+ MplsIntent intent = makeIntent("d8", ingressLabel, "d1", egressLabel);
+ assertThat(intent, is(notNullValue()));
+
+ String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"};
+ MplsIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(Matchers.notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent reverseResultIntent = result.get(0);
+ assertThat(reverseResultIntent instanceof MplsPathIntent, is(true));
+
+ // if statement suppresses static analysis warnings about unchecked cast
+ if (reverseResultIntent instanceof MplsPathIntent) {
+ MplsPathIntent reversePathIntent = (MplsPathIntent) reverseResultIntent;
+ assertThat(reversePathIntent.path().links(), hasSize(hops.length + 1));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d2", "d1"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d3", "d2"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d4", "d3"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d5", "d4"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d6", "d5"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d7", "d6"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d8", "d7"));
+ assertEquals(reversePathIntent.egressLabel(), egressLabel);
+ assertEquals(reversePathIntent.ingressLabel(), ingressLabel);
+ }
+ }
+
+ /**
+ * Tests compilation of the intent which designates two different ports on the same switch.
+ */
+ @Test
+ public void testSameSwitchDifferentPortsIntentCompilation() {
+ ConnectPoint src = new ConnectPoint(deviceId("1"), portNumber(1));
+ ConnectPoint dst = new ConnectPoint(deviceId("1"), portNumber(2));
+ MplsIntent intent = MplsIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(src)
+ .ingressLabel(Optional.empty())
+ .egressPoint(dst)
+ .egressLabel(Optional.empty())
+ .build();
+
+ String[] hops = {"1"};
+ MplsIntentCompiler sut = makeCompiler(hops);
+
+ List<Intent> compiled = sut.compile(intent, null, null);
+
+ assertThat(compiled, hasSize(1));
+ assertThat(compiled.get(0), is(instanceOf(MplsPathIntent.class)));
+ Path path = ((MplsPathIntent) compiled.get(0)).path();
+
+ assertThat(path.links(), hasSize(2));
+ Link firstLink = path.links().get(0);
+ assertThat(firstLink, is(createEdgeLink(src, true)));
+ Link secondLink = path.links().get(1);
+ assertThat(secondLink, is(createEdgeLink(dst, false)));
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java
new file mode 100644
index 00000000..771a9883
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MplsPathIntentCompilerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.intent.MplsPathIntent;
+import org.onosproject.store.trivial.SimpleLinkStore;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.PID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+
+public class MplsPathIntentCompilerTest {
+
+ private final ApplicationId appId = new TestApplicationId("test");
+
+ private final ConnectPoint d1p1 = connectPoint("s1", 0);
+ private final ConnectPoint d2p0 = connectPoint("s2", 0);
+ private final ConnectPoint d2p1 = connectPoint("s2", 1);
+ private final ConnectPoint d3p1 = connectPoint("s3", 1);
+
+ private final TrafficSelector selector = DefaultTrafficSelector.builder().build();
+ private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+ private final Optional<MplsLabel> ingressLabel =
+ Optional.of(MplsLabel.mplsLabel(10));
+ private final Optional<MplsLabel> egressLabel =
+ Optional.of(MplsLabel.mplsLabel(20));
+
+ private final List<Link> links = Arrays.asList(
+ new DefaultLink(PID, d1p1, d2p0, DIRECT),
+ new DefaultLink(PID, d2p1, d3p1, DIRECT)
+ );
+
+ private IdGenerator idGenerator = new MockIdGenerator();
+
+ private final int hops = links.size() - 1;
+ private MplsPathIntent intent;
+ private MplsPathIntentCompiler sut;
+
+ @Before
+ public void setUp() {
+ sut = new MplsPathIntentCompiler();
+ CoreService coreService = createMock(CoreService.class);
+ expect(coreService.registerApplication("org.onosproject.net.intent"))
+ .andReturn(appId);
+ sut.coreService = coreService;
+ sut.linkStore = new SimpleLinkStore();
+ sut.resourceService = new IntentTestsMocks.MockResourceService();
+
+ Intent.bindIdGenerator(idGenerator);
+
+ intent = MplsPathIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .path(new DefaultPath(PID, links, hops))
+ .ingressLabel(ingressLabel)
+ .egressLabel(egressLabel)
+ .priority(55)
+ .build();
+
+ IntentExtensionService intentExtensionService = createMock(IntentExtensionService.class);
+ intentExtensionService.registerCompiler(MplsPathIntent.class, sut);
+ intentExtensionService.unregisterCompiler(MplsPathIntent.class);
+ sut.intentExtensionService = intentExtensionService;
+
+ replay(coreService, intentExtensionService);
+ }
+
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ @Test
+ public void testCompile() {
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(1));
+
+ FlowRule rule = rules.stream()
+ .filter(x -> x.deviceId().equals(d2p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule.deviceId(), is(d2p0.deviceId()));
+
+ sut.deactivate();
+
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java
new file mode 100644
index 00000000..eb7a3936
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Path;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.PathService;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.NetTestTools.connectPoint;
+import static org.onosproject.net.NetTestTools.createPath;
+import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
+
+/**
+ * Unit tests for the MultiPointToSinglePoint intent compiler.
+ */
+public class MultiPointToSinglePointIntentCompilerTest extends AbstractIntentTest {
+
+ private static final ApplicationId APPID = new TestApplicationId("foo");
+
+ private TrafficSelector selector = new IntentTestsMocks.MockSelector();
+ private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
+
+ /**
+ * Mock path service for creating paths within the test.
+ */
+ private static class MockPathService implements PathService {
+
+ final String[] pathHops;
+
+ /**
+ * Constructor that provides a set of hops to mock.
+ *
+ * @param pathHops path hops to mock
+ */
+ MockPathService(String[] pathHops) {
+ this.pathHops = pathHops;
+ }
+
+ @Override
+ public Set<Path> getPaths(ElementId src, ElementId dst) {
+ Set<Path> result = new HashSet<>();
+
+ String[] allHops = new String[pathHops.length + 1];
+ allHops[0] = src.toString();
+ if (pathHops.length != 0) {
+ System.arraycopy(pathHops, 0, allHops, 1, pathHops.length);
+ }
+ result.add(createPath(allHops));
+
+ return result;
+ }
+
+ @Override
+ public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) {
+ return null;
+ }
+ }
+
+ /**
+ * Mocks the device service so that a device appears available in the test.
+ */
+ private static class MockDeviceService extends DeviceServiceAdapter {
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return true;
+ }
+ }
+
+ /**
+ * Creates a MultiPointToSinglePoint intent for a group of ingress points
+ * and an egress point.
+ *
+ * @param ingressIds array of ingress device ids
+ * @param egressId device id of the egress point
+ * @return MultiPointToSinglePoint intent
+ */
+ private MultiPointToSinglePointIntent makeIntent(String[] ingressIds, String egressId) {
+ Set<ConnectPoint> ingressPoints = new HashSet<>();
+ ConnectPoint egressPoint = connectPoint(egressId, 2);
+
+ for (String ingressId : ingressIds) {
+ ingressPoints.add(connectPoint(ingressId, 1));
+ }
+
+ return MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoints(ingressPoints)
+ .egressPoint(egressPoint)
+ .build();
+ }
+
+ /**
+ * Creates a compiler for MultiPointToSinglePoint intents.
+ *
+ * @param hops hops to use while computing paths for this intent
+ * @return MultiPointToSinglePoint intent
+ */
+ private MultiPointToSinglePointIntentCompiler makeCompiler(String[] hops) {
+ MultiPointToSinglePointIntentCompiler compiler =
+ new MultiPointToSinglePointIntentCompiler();
+ compiler.pathService = new MockPathService(hops);
+ compiler.deviceService = new MockDeviceService();
+ return compiler;
+ }
+
+ /**
+ * Tests a single ingress point with 8 hops to its egress point.
+ */
+ @Test
+ public void testSingleLongPathCompilation() {
+
+ String[] ingress = {"ingress"};
+ String egress = "egress";
+
+ MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
+ assertThat(intent, is(notNullValue()));
+
+ String[] hops = {"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8",
+ egress};
+ MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(Matchers.notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent resultIntent = result.get(0);
+ assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
+
+ if (resultIntent instanceof LinkCollectionIntent) {
+ LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
+ assertThat(linkIntent.links(), hasSize(9));
+ assertThat(linkIntent.links(), linksHasPath("ingress", "h1"));
+ assertThat(linkIntent.links(), linksHasPath("h1", "h2"));
+ assertThat(linkIntent.links(), linksHasPath("h2", "h3"));
+ assertThat(linkIntent.links(), linksHasPath("h4", "h5"));
+ assertThat(linkIntent.links(), linksHasPath("h5", "h6"));
+ assertThat(linkIntent.links(), linksHasPath("h7", "h8"));
+ assertThat(linkIntent.links(), linksHasPath("h8", "egress"));
+ }
+ }
+
+ /**
+ * Tests a simple topology where two ingress points share some path segments
+ * and some path segments are not shared.
+ */
+ @Test
+ public void testTwoIngressCompilation() {
+ String[] ingress = {"ingress1", "ingress2"};
+ String egress = "egress";
+
+ MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
+ assertThat(intent, is(notNullValue()));
+
+ final String[] hops = {"inner1", "inner2", egress};
+ MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent resultIntent = result.get(0);
+ assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
+
+ if (resultIntent instanceof LinkCollectionIntent) {
+ LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
+ assertThat(linkIntent.links(), hasSize(4));
+ assertThat(linkIntent.links(), linksHasPath("ingress1", "inner1"));
+ assertThat(linkIntent.links(), linksHasPath("ingress2", "inner1"));
+ assertThat(linkIntent.links(), linksHasPath("inner1", "inner2"));
+ assertThat(linkIntent.links(), linksHasPath("inner2", "egress"));
+ }
+ }
+
+ /**
+ * Tests a large number of ingress points that share a common path to the
+ * egress point.
+ */
+ @Test
+ public void testMultiIngressCompilation() {
+ String[] ingress = {"i1", "i2", "i3", "i4", "i5",
+ "i6", "i7", "i8", "i9", "i10"};
+ String egress = "e";
+
+ MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
+ assertThat(intent, is(notNullValue()));
+
+ final String[] hops = {"n1", egress};
+ MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent resultIntent = result.get(0);
+ assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
+
+ if (resultIntent instanceof LinkCollectionIntent) {
+ LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
+ assertThat(linkIntent.links(), hasSize(ingress.length + 1));
+ for (String ingressToCheck : ingress) {
+ assertThat(linkIntent.links(),
+ linksHasPath(ingressToCheck,
+ "n1"));
+ }
+ assertThat(linkIntent.links(), linksHasPath("n1", egress));
+ }
+ }
+
+ /**
+ * Tests ingress and egress on the same device.
+ */
+ @Test
+ public void testSameDeviceCompilation() {
+ String[] ingress = {"i1", "i2"};
+ String egress = "i1";
+
+ MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
+ assertThat(intent, is(notNullValue()));
+
+ final String[] hops = {"i1", "i2"};
+ MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
+ assertThat(compiler, is(notNullValue()));
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent resultIntent = result.get(0);
+ assertThat(resultIntent, instanceOf(LinkCollectionIntent.class));
+
+ if (resultIntent instanceof LinkCollectionIntent) {
+ LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
+ assertThat(linkIntent.links(), hasSize(ingress.length));
+
+ assertThat(linkIntent.links(), linksHasPath("i2", "i1"));
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java
new file mode 100644
index 00000000..2f40b37a
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/OpticalPathIntentCompilerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.intent.OpticalPathIntent;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.NetTestTools.PID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+import static org.onosproject.net.NetTestTools.createLambda;
+
+public class OpticalPathIntentCompilerTest {
+
+ private CoreService coreService;
+ private IntentExtensionService intentExtensionService;
+ private final IdGenerator idGenerator = new MockIdGenerator();
+ private OpticalPathIntentCompiler sut;
+
+ private final TrafficSelector selector = DefaultTrafficSelector.builder().build();
+ private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+ private final ApplicationId appId = new TestApplicationId("test");
+ private final ProviderId pid = new ProviderId("of", "test");
+ private final ConnectPoint d1p1 = connectPoint("s1", 0);
+ private final ConnectPoint d2p0 = connectPoint("s2", 0);
+ private final ConnectPoint d2p1 = connectPoint("s2", 1);
+ private final ConnectPoint d3p1 = connectPoint("s3", 1);
+ private final ConnectPoint d3p0 = connectPoint("s3", 10);
+ private final ConnectPoint d1p0 = connectPoint("s1", 10);
+
+ private final List<Link> links = Arrays.asList(
+ new DefaultLink(PID, d1p1, d2p0, DIRECT),
+ new DefaultLink(PID, d2p1, d3p1, DIRECT)
+ );
+ private final int hops = links.size() + 1;
+ private OpticalPathIntent intent;
+
+ @Before
+ public void setUp() {
+ sut = new OpticalPathIntentCompiler();
+ coreService = createMock(CoreService.class);
+ expect(coreService.registerApplication("org.onosproject.net.intent"))
+ .andReturn(appId);
+ sut.coreService = coreService;
+
+ Intent.bindIdGenerator(idGenerator);
+
+ intent = OpticalPathIntent.builder()
+ .appId(appId)
+ .src(d1p1)
+ .dst(d3p1)
+ .path(new DefaultPath(PID, links, hops))
+ .lambda(createLambda())
+ .signalType(OchSignalType.FIXED_GRID)
+ .build();
+ intentExtensionService = createMock(IntentExtensionService.class);
+ intentExtensionService.registerCompiler(OpticalPathIntent.class, sut);
+ intentExtensionService.unregisterCompiler(OpticalPathIntent.class);
+ sut.intentManager = intentExtensionService;
+ sut.resourceService = new IntentTestsMocks.MockResourceService();
+
+ replay(coreService, intentExtensionService);
+ }
+
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ @Test
+ public void testCompiler() {
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ rules.stream()
+ .filter(x -> x.deviceId().equals(d1p1.deviceId()))
+ .findFirst()
+ .get();
+
+ rules.stream()
+ .filter(x -> x.deviceId().equals(d2p1.deviceId()))
+ .findFirst()
+ .get();
+
+ rules.stream()
+ .filter(x -> x.deviceId().equals(d3p1.deviceId()))
+ .findFirst()
+ .get();
+
+ rules.forEach(rule -> assertEquals("FlowRule priority is incorrect",
+ intent.priority(), rule.priority()));
+
+ sut.deactivate();
+ }
+
+ //TODO test bidirectional optical paths and verify rules
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java
new file mode 100644
index 00000000..f07bf42c
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PathIntentCompilerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.PID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+
+/**
+ * Unit tests for PathIntentCompiler.
+ */
+public class PathIntentCompilerTest {
+
+ private CoreService coreService;
+ private IntentExtensionService intentExtensionService;
+ private IdGenerator idGenerator = new MockIdGenerator();
+ private PathIntentCompiler sut;
+
+ private final TrafficSelector selector = DefaultTrafficSelector.builder().build();
+ private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+ private final ApplicationId appId = new TestApplicationId("test");
+ private final ProviderId pid = new ProviderId("of", "test");
+ private final ConnectPoint d1p1 = connectPoint("s1", 0);
+ private final ConnectPoint d2p0 = connectPoint("s2", 0);
+ private final ConnectPoint d2p1 = connectPoint("s2", 1);
+ private final ConnectPoint d3p1 = connectPoint("s3", 1);
+ private final ConnectPoint d3p0 = connectPoint("s3", 10);
+ private final ConnectPoint d1p0 = connectPoint("s1", 10);
+ private static final int PRIORITY = 555;
+
+ private final List<Link> links = Arrays.asList(
+ createEdgeLink(d1p0, true),
+ new DefaultLink(PID, d1p1, d2p0, DIRECT),
+ new DefaultLink(PID, d2p1, d3p1, DIRECT),
+ createEdgeLink(d3p0, false)
+ );
+ private final int hops = links.size() - 1;
+ private PathIntent intent;
+
+ /**
+ * Configures objects used in all the test cases.
+ */
+ @Before
+ public void setUp() {
+ sut = new PathIntentCompiler();
+ coreService = createMock(CoreService.class);
+ expect(coreService.registerApplication("org.onosproject.net.intent"))
+ .andReturn(appId);
+ sut.coreService = coreService;
+
+ Intent.bindIdGenerator(idGenerator);
+
+ intent = PathIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .priority(PRIORITY)
+ .path(new DefaultPath(pid, links, hops))
+ .build();
+ intentExtensionService = createMock(IntentExtensionService.class);
+ intentExtensionService.registerCompiler(PathIntent.class, sut);
+ intentExtensionService.unregisterCompiler(PathIntent.class);
+ sut.intentManager = intentExtensionService;
+
+ replay(coreService, intentExtensionService);
+ }
+
+ /**
+ * Tears down objects used in all the test cases.
+ */
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Tests the compilation behavior of the path intent compiler.
+ */
+ @Test
+ public void testCompile() {
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList(), Collections.emptySet());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+
+ FlowRule rule1 = rules.stream()
+ .filter(x -> x.deviceId().equals(d1p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule1.deviceId(), is(d1p0.deviceId()));
+ assertThat(rule1.selector(),
+ is(DefaultTrafficSelector.builder(selector).matchInPort(d1p0.port()).build()));
+ assertThat(rule1.treatment(),
+ is(DefaultTrafficTreatment.builder().setOutput(d1p1.port()).build()));
+ assertThat(rule1.priority(), is(intent.priority()));
+
+ FlowRule rule2 = rules.stream()
+ .filter(x -> x.deviceId().equals(d2p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule2.deviceId(), is(d2p0.deviceId()));
+ assertThat(rule2.selector(),
+ is(DefaultTrafficSelector.builder(selector).matchInPort(d2p0.port()).build()));
+ assertThat(rule2.treatment(),
+ is(DefaultTrafficTreatment.builder().setOutput(d2p1.port()).build()));
+ assertThat(rule2.priority(), is(intent.priority()));
+
+ FlowRule rule3 = rules.stream()
+ .filter(x -> x.deviceId().equals(d3p0.deviceId()))
+ .findFirst()
+ .get();
+ assertThat(rule3.deviceId(), is(d3p1.deviceId()));
+ assertThat(rule3.selector(),
+ is(DefaultTrafficSelector.builder(selector).matchInPort(d3p1.port()).build()));
+ assertThat(rule3.treatment(),
+ is(DefaultTrafficTreatment.builder(treatment).setOutput(d3p0.port()).build()));
+ assertThat(rule3.priority(), is(intent.priority()));
+
+ sut.deactivate();
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java
new file mode 100644
index 00000000..e57d9dbe
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.compiler;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.onlab.util.Bandwidth;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.impl.PathNotFoundException;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.LambdaResource;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.fail;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
+
+/**
+ * Unit tests for the HostToHost intent compiler.
+ */
+public class PointToPointIntentCompilerTest extends AbstractIntentTest {
+
+ private static final ApplicationId APPID = new TestApplicationId("foo");
+
+ private TrafficSelector selector = new IntentTestsMocks.MockSelector();
+ private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
+
+ /**
+ * Creates a PointToPoint intent based on ingress and egress device Ids.
+ *
+ * @param ingressIdString string for id of ingress device
+ * @param egressIdString string for id of egress device
+ * @return PointToPointIntent for the two devices
+ */
+ private PointToPointIntent makeIntent(String ingressIdString,
+ String egressIdString) {
+ return PointToPointIntent.builder()
+ .appId(APPID)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(connectPoint(ingressIdString, 1))
+ .egressPoint(connectPoint(egressIdString, 1))
+ .build();
+ }
+
+ /**
+ * Creates a PointToPoint intent based on ingress and egress deviceIds and constraints.
+ *
+ * @param ingressIdString string for id of ingress device
+ * @param egressIdString string for id of egress device
+ * @param constraints constraints
+ * @return PointToPointIntent for the two device with constraints
+ */
+ private PointToPointIntent makeIntent(String ingressIdString,
+ String egressIdString, List<Constraint> constraints) {
+ return PointToPointIntent.builder()
+ .appId(APPID)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(connectPoint(ingressIdString, 1))
+ .egressPoint(connectPoint(egressIdString, 1))
+ .constraints(constraints)
+ .build();
+ }
+
+ /**
+ * Creates a compiler for HostToHost intents.
+ *
+ * @param hops string array describing the path hops to use when compiling
+ * @return HostToHost intent compiler
+ */
+ private PointToPointIntentCompiler makeCompiler(String[] hops) {
+ PointToPointIntentCompiler compiler = new PointToPointIntentCompiler();
+ compiler.pathService = new IntentTestsMocks.MockPathService(hops);
+ return compiler;
+ }
+
+ /**
+ * Creates a point to point intent compiler for a three switch linear
+ * topology.
+ *
+ * @param resourceService service to use for resource allocation requests
+ * @return point to point compiler
+ */
+ private PointToPointIntentCompiler makeCompiler(String[] hops, LinkResourceService resourceService) {
+ final PointToPointIntentCompiler compiler = new PointToPointIntentCompiler();
+ compiler.resourceService = resourceService;
+ compiler.pathService = new IntentTestsMocks.MockPathService(hops);
+ return compiler;
+ }
+
+ /**
+ * Tests a pair of devices in an 8 hop path, forward direction.
+ */
+ @Test
+ public void testForwardPathCompilation() {
+
+ PointToPointIntent intent = makeIntent("d1", "d8");
+
+ String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"};
+ PointToPointIntentCompiler compiler = makeCompiler(hops);
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(Matchers.notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent forwardResultIntent = result.get(0);
+ assertThat(forwardResultIntent instanceof PathIntent, is(true));
+
+ if (forwardResultIntent instanceof PathIntent) {
+ PathIntent forwardPathIntent = (PathIntent) forwardResultIntent;
+ // 7 links for the hops, plus one default lnk on ingress and egress
+ assertThat(forwardPathIntent.path().links(), hasSize(hops.length + 1));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d1", "d2"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d2", "d3"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d3", "d4"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d4", "d5"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d5", "d6"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d6", "d7"));
+ assertThat(forwardPathIntent.path().links(), linksHasPath("d7", "d8"));
+ }
+ }
+
+ /**
+ * Tests a pair of devices in an 8 hop path, forward direction.
+ */
+ @Test
+ public void testReversePathCompilation() {
+
+ PointToPointIntent intent = makeIntent("d8", "d1");
+
+ String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"};
+ PointToPointIntentCompiler compiler = makeCompiler(hops);
+
+ List<Intent> result = compiler.compile(intent, null, null);
+ assertThat(result, is(Matchers.notNullValue()));
+ assertThat(result, hasSize(1));
+ Intent reverseResultIntent = result.get(0);
+ assertThat(reverseResultIntent instanceof PathIntent, is(true));
+
+ if (reverseResultIntent instanceof PathIntent) {
+ PathIntent reversePathIntent = (PathIntent) reverseResultIntent;
+ assertThat(reversePathIntent.path().links(), hasSize(hops.length + 1));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d2", "d1"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d3", "d2"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d4", "d3"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d5", "d4"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d6", "d5"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d7", "d6"));
+ assertThat(reversePathIntent.path().links(), linksHasPath("d8", "d7"));
+ }
+ }
+
+ /**
+ * Tests compilation of the intent which designates two different ports on the same switch.
+ */
+ @Test
+ public void testSameSwitchDifferentPortsIntentCompilation() {
+ ConnectPoint src = new ConnectPoint(deviceId("1"), portNumber(1));
+ ConnectPoint dst = new ConnectPoint(deviceId("1"), portNumber(2));
+ PointToPointIntent intent = PointToPointIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(src)
+ .egressPoint(dst)
+ .build();
+
+ String[] hops = {"1"};
+ PointToPointIntentCompiler sut = makeCompiler(hops);
+
+ List<Intent> compiled = sut.compile(intent, null, null);
+
+ assertThat(compiled, hasSize(1));
+ assertThat(compiled.get(0), is(instanceOf(PathIntent.class)));
+ Path path = ((PathIntent) compiled.get(0)).path();
+
+ assertThat(path.links(), hasSize(2));
+ Link firstLink = path.links().get(0);
+ assertThat(firstLink, is(createEdgeLink(src, true)));
+ Link secondLink = path.links().get(1);
+ assertThat(secondLink, is(createEdgeLink(dst, false)));
+ }
+
+ /**
+ * Tests that requests with sufficient available bandwidth succeed.
+ */
+ @Test
+ public void testBandwidthConstrainedIntentSuccess() {
+
+ final LinkResourceService resourceService =
+ IntentTestsMocks.MockResourceService.makeBandwidthResourceService(1000.0);
+ final List<Constraint> constraints =
+ Collections.singletonList(new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(100.0))));
+
+ final PointToPointIntent intent = makeIntent("s1", "s3", constraints);
+
+ String[] hops = {"s1", "s2", "s3"};
+ final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService);
+
+ final List<Intent> compiledIntents = compiler.compile(intent, null, null);
+
+ assertThat(compiledIntents, Matchers.notNullValue());
+ assertThat(compiledIntents, hasSize(1));
+ }
+
+ /**
+ * Tests that requests with insufficient available bandwidth fail.
+ */
+ @Test
+ public void testBandwidthConstrainedIntentFailure() {
+
+ final LinkResourceService resourceService =
+ IntentTestsMocks.MockResourceService.makeBandwidthResourceService(10.0);
+ final List<Constraint> constraints =
+ Collections.singletonList(new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(100.0))));
+
+ try {
+ final PointToPointIntent intent = makeIntent("s1", "s3", constraints);
+
+ String[] hops = {"s1", "s2", "s3"};
+ final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService);
+
+ compiler.compile(intent, null, null);
+
+ fail("Point to Point compilation with insufficient bandwidth does "
+ + "not throw exception.");
+ } catch (PathNotFoundException noPath) {
+ assertThat(noPath.getMessage(), containsString("No path"));
+ }
+ }
+
+ /**
+ * Tests that requests for available lambdas are successful.
+ */
+ @Test
+ public void testLambdaConstrainedIntentSuccess() {
+
+ final List<Constraint> constraints =
+ Collections.singletonList(new LambdaConstraint(LambdaResource.valueOf(1)));
+ final LinkResourceService resourceService =
+ IntentTestsMocks.MockResourceService.makeLambdaResourceService(1);
+
+ final PointToPointIntent intent = makeIntent("s1", "s3", constraints);
+
+ String[] hops = {"s1", "s2", "s3"};
+ final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService);
+
+ final List<Intent> compiledIntents =
+ compiler.compile(intent, null, null);
+
+ assertThat(compiledIntents, Matchers.notNullValue());
+ assertThat(compiledIntents, hasSize(1));
+ }
+
+ /**
+ * Tests that requests for lambdas when there are no available lambdas
+ * fail.
+ */
+ @Test
+ public void testLambdaConstrainedIntentFailure() {
+
+ final List<Constraint> constraints =
+ Collections.singletonList(new LambdaConstraint(LambdaResource.valueOf(1)));
+ final LinkResourceService resourceService =
+ IntentTestsMocks.MockResourceService.makeBandwidthResourceService(10.0);
+ try {
+ final PointToPointIntent intent = makeIntent("s1", "s3", constraints);
+
+ String[] hops = {"s1", "s2", "s3"};
+ final PointToPointIntentCompiler compiler = makeCompiler(hops, resourceService);
+
+ compiler.compile(intent, null, null);
+
+ fail("Point to Point compilation with no available lambda does "
+ + "not throw exception.");
+ } catch (PathNotFoundException noPath) {
+ assertThat(noPath.getMessage(), containsString("No path"));
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java
new file mode 100644
index 00000000..c15ecaec
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/intent/impl/phase/CompilingTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.intent.impl.phase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.impl.IntentCompilationException;
+import org.onosproject.net.intent.impl.IntentProcessor;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Timestamp;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.intent.IntentState.INSTALL_REQ;
+
+/**
+ * Unit tests for Compiling phase.
+ */
+public class CompilingTest {
+
+ private final ApplicationId appId = new TestApplicationId("test");
+ private final ProviderId pid = new ProviderId("of", "test");
+ private final TrafficSelector selector = DefaultTrafficSelector.emptySelector();
+ private final TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+ private final ConnectPoint cp1 = new ConnectPoint(deviceId("1"), portNumber(1));
+ private final ConnectPoint cp2 = new ConnectPoint(deviceId("1"), portNumber(2));
+ private final ConnectPoint cp3 = new ConnectPoint(deviceId("2"), portNumber(1));
+ private final ConnectPoint cp4 = new ConnectPoint(deviceId("2"), portNumber(2));
+
+ private final List<Link> links = Collections.singletonList(new DefaultLink(pid, cp2, cp4, DIRECT));
+ private final Path path = new DefaultPath(pid, links, 10);
+
+ private PointToPointIntent input;
+ private PathIntent compiled;
+
+ private IdGenerator idGenerator;
+ private IntentProcessor processor;
+ private Timestamp version;
+
+ @Before
+ public void setUp() {
+ processor = createMock(IntentProcessor.class);
+ version = createMock(Timestamp.class);
+
+ idGenerator = new MockIdGenerator();
+
+ Intent.bindIdGenerator(idGenerator);
+
+ // Intent creation should be placed after binding an ID generator
+ input = PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(cp1)
+ .egressPoint(cp3)
+ .build();
+ compiled = PathIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .path(path)
+ .build();
+ }
+
+
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Tests a next phase when no exception occurs.
+ */
+ @Test
+ public void testMoveToNextPhaseWithoutError() {
+ IntentData pending = new IntentData(input, INSTALL_REQ, version);
+
+ expect(processor.compile(input, null)).andReturn(Collections.singletonList(compiled));
+ replay(processor);
+
+ Compiling sut = new Compiling(processor, pending, Optional.empty());
+
+ Optional<IntentProcessPhase> output = sut.execute();
+
+ verify(processor);
+ assertThat(output.get(), is(instanceOf(Installing.class)));
+ }
+
+ /**
+ * Tests a next phase when IntentCompilationException occurs.
+ */
+ @Test
+ public void testWhenIntentCompilationExceptionOccurs() {
+ IntentData pending = new IntentData(input, INSTALL_REQ, version);
+
+ expect(processor.compile(input, null)).andThrow(new IntentCompilationException());
+ replay(processor);
+
+ Compiling sut = new Compiling(processor, pending, Optional.empty());
+
+ Optional<IntentProcessPhase> output = sut.execute();
+
+ verify(processor);
+ assertThat(output.get(), is(instanceOf(Failed.class)));
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java
new file mode 100644
index 00000000..fe9e37cd
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/BasicLinkOperatorTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.link.impl;
+
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.junit.Assert.assertEquals;
+
+import java.time.Duration;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.config.basics.BasicLinkConfig;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+public class BasicLinkOperatorTest {
+
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+ private static final PortNumber P1 = portNumber(1);
+
+ private static final ConnectPoint SRC = new ConnectPoint(DID1, P1);
+ private static final ConnectPoint DST = new ConnectPoint(DID2, P1);
+ private static final LinkKey LK = LinkKey.linkKey(SRC, DST);
+ private static final Duration NTIME = Duration.ofNanos(200);
+
+ private static final SparseAnnotations SA = DefaultAnnotations.builder()
+ .set(AnnotationKeys.DURABLE, "true").build();
+ private static final LinkDescription LD = new DefaultLinkDescription(SRC, DST, Link.Type.DIRECT, SA);
+ private final ConfigApplyDelegate delegate = new ConfigApplyDelegate() {
+ @Override
+ public void onApply(Config config) {
+ }
+ };
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private static final BasicLinkConfig BLC = new BasicLinkConfig();
+
+ @Before
+ public void setUp() {
+ BLC.init(LK, "optest", JsonNodeFactory.instance.objectNode(), mapper, delegate);
+ BLC.latency(NTIME);
+ }
+
+ @Test
+ public void testDescOps() {
+ LinkDescription desc = BasicLinkOperator.combine(BLC, LD);
+ assertEquals(NTIME.toString(), desc.annotations().value(AnnotationKeys.LATENCY));
+ assertEquals("true", desc.annotations().value(AnnotationKeys.DURABLE));
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java
new file mode 100644
index 00000000..dad5429e
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/link/impl/LinkManagerTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.link.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.event.Event;
+import org.onosproject.net.config.NetworkConfigServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkAdminService;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkListener;
+import org.onosproject.net.link.LinkProvider;
+import org.onosproject.net.link.LinkProviderRegistry;
+import org.onosproject.net.link.LinkProviderService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.device.impl.DeviceManager;
+import org.onosproject.store.trivial.SimpleLinkStore;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.Link.Type.INDIRECT;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+import static org.onosproject.net.link.LinkEvent.Type.*;
+
+/**
+ * Test codifying the link service & link provider service contracts.
+ */
+public class LinkManagerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+ private static final DeviceId DID3 = deviceId("of:goo");
+ private static final Device DEV1 = new DefaultDevice(
+ PID, DID1, Device.Type.SWITCH, "", "", "", "", null);
+ private static final Device DEV2 = new DefaultDevice(
+ PID, DID2, Device.Type.SWITCH, "", "", "", "", null);
+ private static final Device DEV3 = new DefaultDevice(
+ PID, DID2, Device.Type.SWITCH, "", "", "", "", null);
+
+ private static final PortNumber P1 = PortNumber.portNumber(1);
+ private static final PortNumber P2 = PortNumber.portNumber(2);
+ private static final PortNumber P3 = PortNumber.portNumber(3);
+ private static final Map<DeviceId, Device> DEVICEIDMAP = new HashMap<>();
+
+ private LinkManager mgr;
+
+ protected LinkService service;
+ protected LinkAdminService admin;
+ protected LinkProviderRegistry registry;
+ protected LinkProviderService providerService;
+ protected TestProvider provider;
+ protected TestListener listener = new TestListener();
+ protected DeviceManager devmgr = new TestDeviceManager();
+
+
+
+ @Before
+ public void setUp() {
+ mgr = new LinkManager();
+ service = mgr;
+ admin = mgr;
+ registry = mgr;
+ mgr.store = new SimpleLinkStore();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ mgr.deviceService = devmgr;
+ mgr.networkConfigService = new TestNetworkConfigService();
+ mgr.activate();
+
+ DEVICEIDMAP.put(DID1, DEV1);
+ DEVICEIDMAP.put(DID2, DEV2);
+ DEVICEIDMAP.put(DID3, DEV3);
+
+ service.addListener(listener);
+
+ provider = new TestProvider();
+ providerService = registry.register(provider);
+ assertTrue("provider should be registered",
+ registry.getProviders().contains(provider.id()));
+ }
+
+ @After
+ public void tearDown() {
+ registry.unregister(provider);
+ assertFalse("provider should not be registered",
+ registry.getProviders().contains(provider.id()));
+ service.removeListener(listener);
+ mgr.deactivate();
+ }
+
+ @Test
+ public void createLink() {
+ addLink(DID1, P1, DID2, P2, DIRECT);
+ addLink(DID2, P2, DID1, P1, DIRECT);
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+
+ Iterator<Link> it = service.getLinks().iterator();
+ it.next();
+ it.next();
+ assertFalse("incorrect link count", it.hasNext());
+ }
+
+ @Test
+ public void updateLink() {
+ addLink(DID1, P1, DID2, P2, DIRECT);
+ addLink(DID2, P2, DID1, P1, INDIRECT);
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+
+ providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), DIRECT));
+ validateEvents(LINK_UPDATED);
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+
+ providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), INDIRECT));
+ providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), DIRECT));
+ assertEquals("no events expected", 0, listener.events.size());
+ }
+
+ @Test
+ public void removeLink() {
+ addLink(DID1, P1, DID2, P2, DIRECT);
+ addLink(DID2, P2, DID1, P1, DIRECT);
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+
+ providerService.linkVanished(new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P2), DIRECT));
+ validateEvents(LINK_REMOVED);
+ assertEquals("incorrect link count", 1, service.getLinkCount());
+ assertNull("link should not be found", service.getLink(cp(DID1, P1), cp(DID2, P2)));
+ assertNotNull("link should be found", service.getLink(cp(DID2, P2), cp(DID1, P1)));
+
+ providerService.linkVanished(new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P2), DIRECT));
+ assertEquals("no events expected", 0, listener.events.size());
+ }
+
+ @Test
+ public void removeLinksByConnectionPoint() {
+ Link l1 = addLink(DID1, P1, DID2, P2, DIRECT);
+ Link l2 = addLink(DID2, P2, DID1, P1, DIRECT);
+ addLink(DID3, P3, DID2, P1, DIRECT);
+ addLink(DID2, P1, DID3, P3, DIRECT);
+ assertEquals("incorrect link count", 4, service.getLinkCount());
+
+ providerService.linksVanished(cp(DID1, P1));
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+ assertNull("link should be gone", service.getLink(l1.src(), l1.dst()));
+ assertNull("link should be gone", service.getLink(l2.src(), l2.dst()));
+ }
+
+ @Test
+ public void removeLinksByDevice() {
+ addLink(DID1, P1, DID2, P2, DIRECT);
+ addLink(DID2, P2, DID1, P1, DIRECT);
+ addLink(DID3, P3, DID2, P1, DIRECT);
+ addLink(DID2, P1, DID3, P3, DIRECT);
+ Link l5 = addLink(DID3, P1, DID1, P2, DIRECT);
+ Link l6 = addLink(DID1, P2, DID3, P1, DIRECT);
+ assertEquals("incorrect link count", 6, service.getLinkCount());
+
+ providerService.linksVanished(DID2);
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+ assertNotNull("link should not be gone", service.getLink(l5.src(), l5.dst()));
+ assertNotNull("link should not be gone", service.getLink(l6.src(), l6.dst()));
+ }
+
+ @Test
+ public void removeLinksAsAdminByConnectionPoint() {
+ Link l1 = addLink(DID1, P1, DID2, P2, DIRECT);
+ Link l2 = addLink(DID2, P2, DID1, P1, DIRECT);
+ addLink(DID3, P3, DID2, P1, DIRECT);
+ addLink(DID2, P1, DID3, P3, DIRECT);
+ assertEquals("incorrect link count", 4, service.getLinkCount());
+
+ admin.removeLinks(cp(DID1, P1));
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+ assertNull("link should be gone", service.getLink(l1.src(), l1.dst()));
+ assertNull("link should be gone", service.getLink(l2.src(), l2.dst()));
+ }
+
+ @Test
+ public void removeLinksAsAdminByDevice() {
+ addLink(DID1, P1, DID2, P2, DIRECT);
+ addLink(DID2, P2, DID1, P1, DIRECT);
+ addLink(DID3, P3, DID2, P1, DIRECT);
+ addLink(DID2, P1, DID3, P3, DIRECT);
+ Link l5 = addLink(DID3, P1, DID1, P2, DIRECT);
+ Link l6 = addLink(DID1, P2, DID3, P1, DIRECT);
+ assertEquals("incorrect link count", 6, service.getLinkCount());
+
+ admin.removeLinks(DID2);
+ assertEquals("incorrect link count", 2, service.getLinkCount());
+ assertNotNull("link should not be gone", service.getLink(l5.src(), l5.dst()));
+ assertNotNull("link should not be gone", service.getLink(l6.src(), l6.dst()));
+ }
+
+ @Test
+ public void getLinks() {
+ Link l1 = addLink(DID1, P1, DID2, P2, DIRECT);
+ Link l2 = addLink(DID2, P2, DID1, P1, DIRECT);
+ Link l3 = addLink(DID3, P3, DID2, P1, DIRECT);
+ Link l4 = addLink(DID2, P1, DID3, P3, DIRECT);
+ assertEquals("incorrect link count", 4, service.getLinkCount());
+
+ Set<Link> links = service.getLinks(cp(DID1, P1));
+ assertEquals("incorrect links", ImmutableSet.of(l1, l2), links);
+ links = service.getEgressLinks(cp(DID1, P1));
+ assertEquals("incorrect links", ImmutableSet.of(l1), links);
+ links = service.getIngressLinks(cp(DID1, P1));
+ assertEquals("incorrect links", ImmutableSet.of(l2), links);
+
+ links = service.getDeviceLinks(DID2);
+ assertEquals("incorrect links", ImmutableSet.of(l1, l2, l3, l4), links);
+ links = service.getDeviceLinks(DID3);
+ assertEquals("incorrect links", ImmutableSet.of(l3, l4), links);
+
+ links = service.getDeviceEgressLinks(DID2);
+ assertEquals("incorrect links", ImmutableSet.of(l2, l4), links);
+ links = service.getDeviceIngressLinks(DID2);
+ assertEquals("incorrect links", ImmutableSet.of(l1, l3), links);
+ }
+
+
+ private Link addLink(DeviceId sd, PortNumber sp, DeviceId dd, PortNumber dp,
+ Link.Type type) {
+ providerService.linkDetected(new DefaultLinkDescription(cp(sd, sp), cp(dd, dp), type));
+ Link link = listener.events.get(0).subject();
+ validateEvents(LINK_ADDED);
+ return link;
+ }
+
+ private ConnectPoint cp(DeviceId id, PortNumber portNumber) {
+ return new ConnectPoint(id, portNumber);
+ }
+
+ protected void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("wrong events received", types.length, listener.events.size());
+ for (Event event : listener.events) {
+ assertEquals("incorrect event type", types[i], event.type());
+ i++;
+ }
+ listener.events.clear();
+ }
+
+
+ private class TestProvider extends AbstractProvider implements LinkProvider {
+ private Device deviceReceived;
+ private MastershipRole roleReceived;
+
+ public TestProvider() {
+ super(PID);
+ }
+ }
+
+ private static class TestListener implements LinkListener {
+ final List<LinkEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(LinkEvent event) {
+ events.add(event);
+ }
+ }
+
+ private static class TestDeviceManager extends DeviceManager {
+
+ @Override
+ public MastershipRole getRole(DeviceId deviceId) {
+ return MastershipRole.MASTER;
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return DEVICEIDMAP.get(deviceId);
+ }
+
+ }
+ private class TestNetworkConfigService extends NetworkConfigServiceAdapter {
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
new file mode 100644
index 00000000..1a160d98
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.proxyarp.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.edgeservice.impl.EdgeManager;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.link.LinkListener;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.proxyarp.ProxyArpStore;
+import org.onosproject.net.proxyarp.ProxyArpStoreDelegate;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link ProxyArpManager} class.
+ */
+public class ProxyArpManagerTest {
+
+ private static final int NUM_DEVICES = 6;
+ private static final int NUM_PORTS_PER_DEVICE = 3;
+ private static final int NUM_ADDRESS_PORTS = NUM_DEVICES / 2;
+ private static final int NUM_FLOOD_PORTS = 3;
+
+ private static final Ip4Address IP1 = Ip4Address.valueOf("192.168.1.1");
+ private static final Ip4Address IP2 = Ip4Address.valueOf("192.168.1.2");
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+
+ private static final VlanId VLAN1 = VlanId.vlanId((short) 1);
+ private static final VlanId VLAN2 = VlanId.vlanId((short) 2);
+ private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01");
+ private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
+ private static final HostId HID1 = HostId.hostId(MAC1, VLAN1);
+ private static final HostId HID2 = HostId.hostId(MAC2, VLAN1);
+
+ private static final DeviceId DID1 = getDeviceId(1);
+ private static final DeviceId DID2 = getDeviceId(2);
+ private static final PortNumber P1 = PortNumber.portNumber(1);
+ private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L);
+ private static final HostLocation LOC2 = new HostLocation(DID2, P1, 123L);
+ private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes();
+
+ //Return values used for various functions of the TestPacketService inner class.
+ private boolean isEdgePointReturn;
+ private List<ConnectPoint> getEdgePointsNoArg;
+
+
+ private ProxyArpManager proxyArp;
+
+ private TestPacketService packetService;
+ private DeviceService deviceService;
+ private LinkService linkService;
+ private HostService hostService;
+ private InterfaceService interfaceService;
+
+ @Before
+ public void setUp() throws Exception {
+ proxyArp = new ProxyArpManager();
+ packetService = new TestPacketService();
+ proxyArp.packetService = packetService;
+ proxyArp.store = new TestProxyArpStoreAdapter();
+
+ proxyArp.edgeService = new TestEdgePortService();
+
+ // Create a host service mock here. Must be replayed by tests once the
+ // expectations have been set up
+ hostService = createMock(HostService.class);
+ proxyArp.hostService = hostService;
+
+ interfaceService = createMock(InterfaceService.class);
+ proxyArp.interfaceService = interfaceService;
+
+ createTopology();
+ proxyArp.deviceService = deviceService;
+ proxyArp.linkService = linkService;
+
+ proxyArp.activate();
+ }
+
+ /**
+ * Creates a fake topology to feed into the ARP module.
+ * <p>
+ * The default topology is a unidirectional ring topology. Each switch has
+ * 3 ports. Ports 2 and 3 have the links to neighbor switches, and port 1
+ * is free (edge port).
+ * The first half of the switches have IP addresses configured on their
+ * free ports (port 1). The second half of the switches have no IP
+ * addresses configured.
+ */
+ private void createTopology() {
+ deviceService = createMock(DeviceService.class);
+ linkService = createMock(LinkService.class);
+
+ deviceService.addListener(anyObject(DeviceListener.class));
+ linkService.addListener(anyObject(LinkListener.class));
+
+ createDevices(NUM_DEVICES, NUM_PORTS_PER_DEVICE);
+ createLinks(NUM_DEVICES);
+ addAddressBindings();
+ }
+
+ /**
+ * Creates the devices for the fake topology.
+ */
+ private void createDevices(int numDevices, int numPorts) {
+ List<Device> devices = new ArrayList<>();
+
+ for (int i = 1; i <= numDevices; i++) {
+ DeviceId devId = getDeviceId(i);
+ Device device = createMock(Device.class);
+ expect(device.id()).andReturn(devId).anyTimes();
+ replay(device);
+
+ devices.add(device);
+
+ List<Port> ports = new ArrayList<>();
+ for (int j = 1; j <= numPorts; j++) {
+ Port port = createMock(Port.class);
+ expect(port.number()).andReturn(PortNumber.portNumber(j)).anyTimes();
+ replay(port);
+ ports.add(port);
+ }
+
+ expect(deviceService.getPorts(devId)).andReturn(ports).anyTimes();
+ expect(deviceService.getDevice(devId)).andReturn(device).anyTimes();
+ }
+
+ expect(deviceService.getDevices()).andReturn(devices).anyTimes();
+ replay(deviceService);
+ }
+
+ /**
+ * Creates the links for the fake topology.
+ * NB: Only unidirectional links are created, as for this purpose all we
+ * need is to occupy the ports with some link.
+ */
+ private void createLinks(int numDevices) {
+ List<Link> links = new ArrayList<>();
+
+ for (int i = 1; i <= numDevices; i++) {
+ ConnectPoint src = new ConnectPoint(
+ getDeviceId(i),
+ PortNumber.portNumber(2));
+ ConnectPoint dst = new ConnectPoint(
+ getDeviceId((i + 1 > numDevices) ? 1 : i + 1),
+ PortNumber.portNumber(3));
+
+ Link link = createMock(Link.class);
+ expect(link.src()).andReturn(src).anyTimes();
+ expect(link.dst()).andReturn(dst).anyTimes();
+ replay(link);
+
+ links.add(link);
+ }
+
+ expect(linkService.getLinks()).andReturn(links).anyTimes();
+ replay(linkService);
+ }
+
+ private void addAddressBindings() {
+ Set<Interface> interfaces = Sets.newHashSet();
+
+ for (int i = 1; i <= NUM_ADDRESS_PORTS; i++) {
+ ConnectPoint cp = new ConnectPoint(getDeviceId(i), P1);
+ Ip4Prefix prefix1 =
+ Ip4Prefix.valueOf("10.0." + (2 * i - 1) + ".0/24");
+ Ip4Address addr1 =
+ Ip4Address.valueOf("10.0." + (2 * i - 1) + ".1");
+ Ip4Prefix prefix2 = Ip4Prefix.valueOf("10.0." + (2 * i) + ".0/24");
+ Ip4Address addr2 = Ip4Address.valueOf("10.0." + (2 * i) + ".1");
+ InterfaceIpAddress ia1 = new InterfaceIpAddress(addr1, prefix1);
+ InterfaceIpAddress ia2 = new InterfaceIpAddress(addr2, prefix2);
+ Interface intf1 = new Interface(cp, Sets.newHashSet(ia1),
+ MacAddress.valueOf(2 * i - 1),
+ VlanId.vlanId((short) 1));
+ Interface intf2 = new Interface(cp, Sets.newHashSet(ia2),
+ MacAddress.valueOf(2 * i),
+ VlanId.NONE);
+
+ interfaces.add(intf1);
+ interfaces.add(intf2);
+
+ expect(interfaceService.getInterfacesByPort(cp))
+ .andReturn(Sets.newHashSet(intf1, intf2)).anyTimes();
+ }
+
+ expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes();
+
+ for (int i = 1; i <= NUM_FLOOD_PORTS; i++) {
+ ConnectPoint cp = new ConnectPoint(getDeviceId(i + NUM_ADDRESS_PORTS),
+ P1);
+
+ expect(interfaceService.getInterfacesByPort(cp))
+ .andReturn(Collections.emptySet()).anyTimes();
+ }
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#isKnown(org.onlab.packet.IpAddress)} in the
+ * case where the IP address is not known.
+ * Verifies the method returns false.
+ */
+ @Test
+ public void testNotKnown() {
+ expect(hostService.getHostsByIp(IP1)).andReturn(Collections.<Host>emptySet());
+ replay(hostService);
+ replay(interfaceService);
+
+ assertFalse(proxyArp.isKnown(IP1));
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#isKnown(org.onlab.packet.IpAddress)} in the
+ * case where the IP address is known.
+ * Verifies the method returns true.
+ */
+ @Test
+ public void testKnown() {
+ Host host1 = createMock(Host.class);
+ Host host2 = createMock(Host.class);
+
+ expect(hostService.getHostsByIp(IP1))
+ .andReturn(Sets.newHashSet(host1, host2));
+ replay(hostService);
+ replay(interfaceService);
+
+ assertTrue(proxyArp.isKnown(IP1));
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
+ * destination host is known.
+ * Verifies the correct ARP reply is sent out the correct port.
+ */
+ @Test
+ public void testReplyKnown() {
+ //Set the return value of isEdgePoint from the edgemanager.
+ isEdgePointReturn = true;
+
+ Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN1, getLocation(4),
+ Collections.singleton(IP1));
+
+ Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(5),
+ Collections.singleton(IP2));
+
+ expect(hostService.getHostsByIp(IP1))
+ .andReturn(Collections.singleton(replyer));
+ expect(hostService.getHost(HID2)).andReturn(requestor);
+
+ replay(hostService);
+ replay(interfaceService);
+
+ Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1);
+
+ proxyArp.reply(arpRequest, getLocation(5));
+
+ assertEquals(1, packetService.packets.size());
+ Ethernet arpReply = buildArp(ARP.OP_REPLY, MAC1, MAC2, IP1, IP2);
+ verifyPacketOut(arpReply, getLocation(5), packetService.packets.get(0));
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
+ * destination host is not known.
+ * Verifies the ARP request is flooded out the correct edge ports.
+ */
+ @Test
+ public void testReplyUnknown() {
+ isEdgePointReturn = true;
+
+ Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(5),
+ Collections.singleton(IP2));
+
+ expect(hostService.getHostsByIp(IP1))
+ .andReturn(Collections.emptySet());
+ expect(interfaceService.getInterfacesByIp(IP2))
+ .andReturn(Collections.emptySet());
+ expect(hostService.getHost(HID2)).andReturn(requestor);
+
+
+ replay(hostService);
+ replay(interfaceService);
+
+ Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1);
+
+ //Setup the set of edge ports to be used in the reply method
+ getEdgePointsNoArg = Lists.newLinkedList();
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1)));
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1)));
+
+ proxyArp.reply(arpRequest, getLocation(6));
+
+ verifyFlood(arpRequest);
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
+ * destination host is known for that IP address, but is not on the same
+ * VLAN as the source host.
+ * Verifies the ARP request is flooded out the correct edge ports.
+ */
+ @Test
+ public void testReplyDifferentVlan() {
+
+ Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN2, getLocation(4),
+ Collections.singleton(IP1));
+
+ Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, getLocation(5),
+ Collections.singleton(IP2));
+
+ expect(hostService.getHostsByIp(IP1))
+ .andReturn(Collections.singleton(replyer));
+ expect(interfaceService.getInterfacesByIp(IP2))
+ .andReturn(Collections.emptySet());
+ expect(hostService.getHost(HID2)).andReturn(requestor);
+
+ replay(hostService);
+ replay(interfaceService);
+
+ Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1);
+
+ //Setup for flood test
+ getEdgePointsNoArg = Lists.newLinkedList();
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1)));
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1)));
+ proxyArp.reply(arpRequest, getLocation(6));
+
+ verifyFlood(arpRequest);
+ }
+
+ @Test
+ public void testReplyToRequestForUs() {
+ Ip4Address theirIp = Ip4Address.valueOf("10.0.1.254");
+ Ip4Address ourFirstIp = Ip4Address.valueOf("10.0.1.1");
+ Ip4Address ourSecondIp = Ip4Address.valueOf("10.0.2.1");
+ MacAddress firstMac = MacAddress.valueOf(1L);
+ MacAddress secondMac = MacAddress.valueOf(2L);
+
+ Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
+ Collections.singleton(theirIp));
+
+ expect(hostService.getHost(HID2)).andReturn(requestor);
+ replay(hostService);
+ replay(interfaceService);
+
+ Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, theirIp, ourFirstIp);
+ isEdgePointReturn = true;
+ proxyArp.reply(arpRequest, LOC1);
+
+ assertEquals(1, packetService.packets.size());
+ Ethernet arpReply = buildArp(ARP.OP_REPLY, firstMac, MAC2, ourFirstIp, theirIp);
+ verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
+
+ // Test a request for the second address on that port
+ packetService.packets.clear();
+ arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, theirIp, ourSecondIp);
+
+ proxyArp.reply(arpRequest, LOC1);
+
+ assertEquals(1, packetService.packets.size());
+ arpReply = buildArp(ARP.OP_REPLY, secondMac, MAC2, ourSecondIp, theirIp);
+ verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
+ }
+
+ @Test
+ public void testReplyExternalPortBadRequest() {
+ replay(hostService); // no further host service expectations
+ replay(interfaceService);
+
+ Ip4Address theirIp = Ip4Address.valueOf("10.0.1.254");
+
+ // Request for a valid external IP address but coming in the wrong port
+ Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC1, null, theirIp,
+ Ip4Address.valueOf("10.0.3.1"));
+ proxyArp.reply(arpRequest, LOC1);
+ assertEquals(0, packetService.packets.size());
+
+ // Request for a valid internal IP address but coming in an external port
+ packetService.packets.clear();
+ arpRequest = buildArp(ARP.OP_REQUEST, MAC1, null, theirIp, IP1);
+ proxyArp.reply(arpRequest, LOC1);
+ assertEquals(0, packetService.packets.size());
+ }
+
+ @Test
+ public void testReplyToRequestFromUs() {
+ Ip4Address ourIp = Ip4Address.valueOf("10.0.1.1");
+ MacAddress ourMac = MacAddress.valueOf(1L);
+ Ip4Address theirIp = Ip4Address.valueOf("10.0.1.100");
+
+ expect(hostService.getHostsByIp(theirIp)).andReturn(Collections.emptySet());
+ expect(interfaceService.getInterfacesByIp(ourIp))
+ .andReturn(Collections.singleton(new Interface(getLocation(1),
+ Collections.singleton(new InterfaceIpAddress(ourIp, IpPrefix.valueOf("10.0.1.1/24"))),
+ ourMac, VLAN1)));
+ expect(hostService.getHost(HostId.hostId(ourMac, VLAN1))).andReturn(null);
+ replay(hostService);
+ replay(interfaceService);
+
+ // This is a request from something inside our network (like a BGP
+ // daemon) to an external host.
+ Ethernet arpRequest = buildArp(ARP.OP_REQUEST, ourMac, null, ourIp, theirIp);
+ //Ensure the packet is allowed through (it is not to an internal port)
+ isEdgePointReturn = true;
+
+ proxyArp.reply(arpRequest, getLocation(5));
+ assertEquals(1, packetService.packets.size());
+ verifyPacketOut(arpRequest, getLocation(1), packetService.packets.get(0));
+
+ // The same request from a random external port should fail
+ packetService.packets.clear();
+ proxyArp.reply(arpRequest, getLocation(2));
+ assertEquals(0, packetService.packets.size());
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the
+ * destination host is known.
+ * Verifies the correct ARP request is sent out the correct port.
+ */
+ @Test
+ public void testForwardToHost() {
+ Host host1 = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1,
+ Collections.singleton(IP1));
+ Host host2 = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC2,
+ Collections.singleton(IP2));
+
+ expect(hostService.getHost(HID1)).andReturn(host1);
+ expect(hostService.getHost(HID2)).andReturn(host2);
+ replay(hostService);
+ replay(interfaceService);
+
+ Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1);
+
+ proxyArp.forward(arpRequest, LOC2);
+
+ assertEquals(1, packetService.packets.size());
+ OutboundPacket packet = packetService.packets.get(0);
+
+ verifyPacketOut(arpRequest, LOC1, packet);
+ }
+
+ /**
+ * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the
+ * destination host is not known.
+ * Verifies the correct ARP request is flooded out the correct edge ports.
+ */
+ @Test
+ public void testForwardFlood() {
+ expect(hostService.getHost(HID1)).andReturn(null);
+ replay(hostService);
+ replay(interfaceService);
+
+ Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1);
+
+ //populate the list of edges when so that when forward hits flood in the manager it contains the values
+ //that should continue on
+ getEdgePointsNoArg = Lists.newLinkedList();
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("3"), PortNumber.portNumber(1)));
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1)));
+ getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1)));
+
+ proxyArp.forward(arpRequest, getLocation(6));
+
+ verifyFlood(arpRequest);
+ }
+
+ /**
+ * Verifies that the given packet was flooded out all available edge ports,
+ * except for the input port.
+ *
+ * @param packet the packet that was expected to be flooded
+ */
+ private void verifyFlood(Ethernet packet) {
+ // There should be 1 less than NUM_FLOOD_PORTS; the inPort should be excluded.
+ assertEquals(NUM_FLOOD_PORTS - 1, packetService.packets.size());
+
+ Collections.sort(packetService.packets,
+ (o1, o2) -> o1.sendThrough().uri().compareTo(o2.sendThrough().uri()));
+
+
+ for (int i = 0; i < NUM_FLOOD_PORTS - 1; i++) {
+ ConnectPoint cp = new ConnectPoint(getDeviceId(NUM_ADDRESS_PORTS + i + 1),
+ PortNumber.portNumber(1));
+
+ OutboundPacket outboundPacket = packetService.packets.get(i);
+ verifyPacketOut(packet, cp, outboundPacket);
+ }
+ }
+
+ /**
+ * Verifies the given packet was sent out the given port.
+ *
+ * @param expected the packet that was expected to be sent
+ * @param outPort the port the packet was expected to be sent out
+ * @param actual the actual OutboundPacket to verify
+ */
+ private void verifyPacketOut(Ethernet expected, ConnectPoint outPort,
+ OutboundPacket actual) {
+ assertArrayEquals(expected.serialize(), actual.data().array());
+ assertEquals(1, actual.treatment().immediate().size());
+ assertEquals(outPort.deviceId(), actual.sendThrough());
+ Instruction instruction = actual.treatment().immediate().get(0);
+ assertTrue(instruction instanceof OutputInstruction);
+ assertEquals(outPort.port(), ((OutputInstruction) instruction).port());
+ }
+
+ /**
+ * Returns the device ID of the ith device.
+ *
+ * @param i device to get the ID of
+ * @return the device ID
+ */
+ private static DeviceId getDeviceId(int i) {
+ return DeviceId.deviceId("" + i);
+ }
+
+ private static HostLocation getLocation(int i) {
+ return new HostLocation(new ConnectPoint(getDeviceId(i), P1), 123L);
+ }
+
+ /**
+ * Builds an ARP packet with the given parameters.
+ *
+ * @param opcode opcode of the ARP packet
+ * @param srcMac source MAC address
+ * @param dstMac destination MAC address, or null if this is a request
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ * @return the ARP packet
+ */
+ private Ethernet buildArp(short opcode, MacAddress srcMac, MacAddress dstMac,
+ Ip4Address srcIp, Ip4Address dstIp) {
+ Ethernet eth = new Ethernet();
+
+ if (dstMac == null) {
+ eth.setDestinationMACAddress(MacAddress.BROADCAST);
+ } else {
+ eth.setDestinationMACAddress(dstMac);
+ }
+
+ eth.setSourceMACAddress(srcMac);
+ eth.setEtherType(Ethernet.TYPE_ARP);
+ eth.setVlanID(VLAN1.toShort());
+
+ ARP arp = new ARP();
+ arp.setOpCode(opcode);
+ arp.setProtocolType(ARP.PROTO_TYPE_IP);
+ arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
+
+ arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
+ arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
+ arp.setSenderHardwareAddress(srcMac.toBytes());
+
+ if (dstMac == null) {
+ arp.setTargetHardwareAddress(ZERO_MAC_ADDRESS);
+ } else {
+ arp.setTargetHardwareAddress(dstMac.toBytes());
+ }
+
+ arp.setSenderProtocolAddress(srcIp.toOctets());
+ arp.setTargetProtocolAddress(dstIp.toOctets());
+
+ eth.setPayload(arp);
+ return eth;
+ }
+
+ /**
+ * Test PacketService implementation that simply stores OutboundPackets
+ * passed to {@link #emit(OutboundPacket)} for later verification.
+ */
+ class TestPacketService extends PacketServiceAdapter {
+
+ List<OutboundPacket> packets = new ArrayList<>();
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ packets.add(packet);
+ }
+
+ }
+
+ class TestEdgePortService extends EdgeManager {
+
+ @Override
+ public boolean isEdgePoint(ConnectPoint connectPoint) {
+ return isEdgePointReturn;
+ }
+
+ @Override
+ public Iterable<ConnectPoint> getEdgePoints() {
+ return getEdgePointsNoArg;
+ }
+ }
+
+ private class TestProxyArpStoreAdapter implements ProxyArpStore {
+ @Override
+ public void forward(ConnectPoint outPort, Host subject, ByteBuffer packet) {
+ TrafficTreatment tt = DefaultTrafficTreatment.builder().setOutput(outPort.port()).build();
+ packetService.emit(new DefaultOutboundPacket(outPort.deviceId(), tt, packet));
+ }
+
+ @Override
+ public void setDelegate(ProxyArpStoreDelegate delegate) {
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java
new file mode 100644
index 00000000..022df23d
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/DefaultTopologyProviderTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.topology.impl;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.event.Event;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.Device;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.impl.DeviceManager;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.impl.LinkManager;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.GraphDescription;
+import org.onosproject.net.topology.TopologyProvider;
+import org.onosproject.net.topology.TopologyProviderRegistry;
+import org.onosproject.net.topology.TopologyProviderService;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import static org.onosproject.net.NetTestTools.device;
+import static org.onosproject.net.NetTestTools.link;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
+
+/**
+ * Test of the default topology provider implementation.
+ */
+public class DefaultTopologyProviderTest {
+
+ private DefaultTopologyProvider provider = new DefaultTopologyProvider();
+ private TestTopoRegistry topologyService = new TestTopoRegistry();
+ private TestDeviceService deviceService = new TestDeviceService();
+ private TestLinkService linkService = new TestLinkService();
+ private TestTopoProviderService providerService;
+
+ // phase corresponds to number of topologyChanged called
+ private Phaser topologyChangedCounts = new Phaser(1);
+
+ @Before
+ public void setUp() {
+ provider.deviceService = deviceService;
+ provider.linkService = linkService;
+ provider.providerRegistry = topologyService;
+ provider.cfgService = new ComponentConfigAdapter();
+ provider.activate(null);
+ }
+
+ @After
+ public void tearDown() {
+ provider.deactivate(null);
+ provider.providerRegistry = null;
+ provider.deviceService = null;
+ provider.linkService = null;
+ provider.cfgService = null;
+ }
+
+ private void validateSubmission() {
+ assertNotNull("registration expected", providerService);
+ assertEquals("incorrect provider", provider, providerService.provider());
+ assertNotNull("topo change should be submitted", providerService.graphDesc);
+ assertEquals("incorrect vertex count", 6, providerService.graphDesc.vertexes().size());
+ assertEquals("incorrect edge count", 10, providerService.graphDesc.edges().size());
+ }
+
+ @Test
+ public void basics() throws InterruptedException, TimeoutException {
+ assertEquals(1, topologyChangedCounts.awaitAdvanceInterruptibly(0, 1, TimeUnit.SECONDS));
+ validateSubmission();
+ }
+
+ @Test
+ public void eventDriven() throws InterruptedException, TimeoutException {
+ assertEquals(1, topologyChangedCounts.awaitAdvanceInterruptibly(0, 1, TimeUnit.SECONDS));
+ validateSubmission();
+
+ deviceService.postEvent(new DeviceEvent(DEVICE_ADDED, device("z"), null));
+ linkService.postEvent(new LinkEvent(LINK_ADDED, link("z", 1, "a", 4)));
+ assertThat(topologyChangedCounts.awaitAdvanceInterruptibly(1, 1, TimeUnit.SECONDS),
+ is(greaterThanOrEqualTo(2)));
+ // Note: posting event, to trigger topologyChanged call,
+ // but dummy topology will not change.
+ validateSubmission();
+ }
+
+
+ private class TestTopoRegistry implements TopologyProviderRegistry {
+
+ @Override
+ public TopologyProviderService register(TopologyProvider provider) {
+ providerService = new TestTopoProviderService(provider);
+ return providerService;
+ }
+
+ @Override
+ public void unregister(TopologyProvider provider) {
+ }
+
+ @Override
+ public Set<ProviderId> getProviders() {
+ return null;
+ }
+ }
+
+ private class TestTopoProviderService
+ extends AbstractProviderService<TopologyProvider>
+ implements TopologyProviderService {
+ GraphDescription graphDesc;
+
+ protected TestTopoProviderService(TopologyProvider provider) {
+ super(provider);
+ }
+
+ @Override
+ public void topologyChanged(GraphDescription graphDescription, List<Event> reasons) {
+ graphDesc = graphDescription;
+ topologyChangedCounts.arrive();
+ }
+ }
+
+ private class TestDeviceService extends DeviceManager {
+ TestDeviceService() {
+ eventDispatcher = new TestEventDispatcher();
+ eventDispatcher.addSink(DeviceEvent.class, listenerRegistry);
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return ImmutableSet.of(device("a"), device("b"),
+ device("c"), device("d"),
+ device("e"), device("f"));
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return getDevices();
+ }
+
+ void postEvent(DeviceEvent event) {
+ eventDispatcher.post(event);
+ }
+ }
+
+ private class TestLinkService extends LinkManager {
+ TestLinkService() {
+ eventDispatcher = new TestEventDispatcher();
+ eventDispatcher.addSink(LinkEvent.class, listenerRegistry);
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return ImmutableSet.of(link("a", 1, "b", 1), link("b", 1, "a", 1),
+ link("b", 2, "c", 1), link("c", 1, "b", 2),
+ link("c", 2, "d", 1), link("d", 1, "c", 2),
+ link("d", 2, "a", 2), link("a", 2, "d", 2),
+ link("e", 1, "f", 1), link("f", 1, "e", 1));
+ }
+
+ @Override
+ public Iterable<Link> getActiveLinks() {
+ return getLinks();
+ }
+
+ void postEvent(LinkEvent event) {
+ eventDispatcher.post(event);
+ }
+ }
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java
new file mode 100644
index 00000000..2a2d0b54
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/PathManagerTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.topology.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Path;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.net.topology.TopologyServiceAdapter;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.NetTestTools.*;
+
+/**
+ * Test of the path selection subsystem.
+ */
+public class PathManagerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+
+ private PathManager mgr;
+ private PathService service;
+
+ private FakeTopoMgr fakeTopoMgr = new FakeTopoMgr();
+ private FakeHostMgr fakeHostMgr = new FakeHostMgr();
+
+ @Before
+ public void setUp() {
+ mgr = new PathManager();
+ service = mgr;
+ mgr.topologyService = fakeTopoMgr;
+ mgr.hostService = fakeHostMgr;
+ mgr.activate();
+ }
+
+ @After
+ public void tearDown() {
+ mgr.deactivate();
+ }
+
+ @Test
+ public void infraToInfra() {
+ DeviceId src = did("src");
+ DeviceId dst = did("dst");
+ fakeTopoMgr.paths.add(createPath("src", "middle", "dst"));
+ Set<Path> paths = service.getPaths(src, dst);
+ validatePaths(paths, 1, 2, src, dst);
+ }
+
+ @Test
+ public void infraToEdge() {
+ DeviceId src = did("src");
+ HostId dst = hid("12:34:56:78:90:ab/1");
+ fakeTopoMgr.paths.add(createPath("src", "middle", "edge"));
+ fakeHostMgr.hosts.put(dst, host("12:34:56:78:90:ab/1", "edge"));
+ Set<Path> paths = service.getPaths(src, dst);
+ validatePaths(paths, 1, 3, src, dst);
+ }
+
+ @Test
+ public void edgeToInfra() {
+ HostId src = hid("12:34:56:78:90:ab/1");
+ DeviceId dst = did("dst");
+ fakeTopoMgr.paths.add(createPath("edge", "middle", "dst"));
+ fakeHostMgr.hosts.put(src, host("12:34:56:78:90:ab/1", "edge"));
+ Set<Path> paths = service.getPaths(src, dst);
+ validatePaths(paths, 1, 3, src, dst);
+ }
+
+ @Test
+ public void edgeToEdge() {
+ HostId src = hid("12:34:56:78:90:ab/1");
+ HostId dst = hid("12:34:56:78:90:ef/1");
+ fakeTopoMgr.paths.add(createPath("srcEdge", "middle", "dstEdge"));
+ fakeHostMgr.hosts.put(src, host("12:34:56:78:90:ab/1", "srcEdge"));
+ fakeHostMgr.hosts.put(dst, host("12:34:56:78:90:ef/1", "dstEdge"));
+ Set<Path> paths = service.getPaths(src, dst);
+ validatePaths(paths, 1, 4, src, dst);
+ }
+
+ @Test
+ public void edgeToEdgeDirect() {
+ HostId src = hid("12:34:56:78:90:ab/1");
+ HostId dst = hid("12:34:56:78:90:ef/1");
+ fakeHostMgr.hosts.put(src, host("12:34:56:78:90:ab/1", "edge"));
+ fakeHostMgr.hosts.put(dst, host("12:34:56:78:90:ef/1", "edge"));
+ Set<Path> paths = service.getPaths(src, dst);
+ validatePaths(paths, 1, 2, src, dst);
+ }
+
+ @Test
+ public void noEdge() {
+ Set<Path> paths = service.getPaths(hid("12:34:56:78:90:ab/1"),
+ hid("12:34:56:78:90:ef/1"));
+ assertTrue("there should be no paths", paths.isEmpty());
+ }
+
+ // Makes sure the set of paths meets basic expectations.
+ private void validatePaths(Set<Path> paths, int count, int length,
+ ElementId src, ElementId dst) {
+ assertEquals("incorrect path count", count, paths.size());
+ for (Path path : paths) {
+ assertEquals("incorrect length", length, path.links().size());
+ assertEquals("incorrect source", src, path.src().elementId());
+ assertEquals("incorrect destination", dst, path.dst().elementId());
+ }
+ }
+
+ // Fake entity to give out paths.
+ private class FakeTopoMgr extends TopologyServiceAdapter implements TopologyService {
+ Set<Path> paths = new HashSet<>();
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+ return paths;
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst, LinkWeight weight) {
+ return paths;
+ }
+ }
+
+ // Fake entity to give out hosts.
+ private class FakeHostMgr extends HostServiceAdapter implements HostService {
+ private Map<HostId, Host> hosts = new HashMap<>();
+
+ @Override
+ public Host getHost(HostId hostId) {
+ return hosts.get(hostId);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java
new file mode 100644
index 00000000..f3cd28df
--- /dev/null
+++ b/framework/src/onos/core/net/src/test/java/org/onosproject/net/topology/impl/TopologyManagerTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.topology.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.event.Event;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.DefaultGraphDescription;
+import org.onosproject.net.topology.GraphDescription;
+import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyGraph;
+import org.onosproject.net.topology.TopologyListener;
+import org.onosproject.net.topology.TopologyProvider;
+import org.onosproject.net.topology.TopologyProviderRegistry;
+import org.onosproject.net.topology.TopologyProviderService;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.store.trivial.SimpleTopologyStore;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.junit.Assert.*;
+import static org.onosproject.net.NetTestTools.*;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.topology.ClusterId.clusterId;
+import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED;
+
+/**
+ * Test of the topology subsystem.
+ */
+public class TopologyManagerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+
+ private TopologyManager mgr;
+
+ protected TopologyService service;
+ protected TopologyProviderRegistry registry;
+ protected TopologyProviderService providerService;
+ protected TestProvider provider;
+ protected TestListener listener = new TestListener();
+
+ @Before
+ public void setUp() {
+ mgr = new TopologyManager();
+ service = mgr;
+ registry = mgr;
+
+ mgr.store = new SimpleTopologyStore();
+ injectEventDispatcher(mgr, new TestEventDispatcher());
+ mgr.activate();
+
+ service.addListener(listener);
+
+ provider = new TestProvider();
+ providerService = registry.register(provider);
+
+ assertTrue("provider should be registered",
+ registry.getProviders().contains(provider.id()));
+ }
+
+ @After
+ public void tearDown() {
+ mgr.deactivate();
+ service.removeListener(listener);
+ }
+
+ @Test
+ public void basics() {
+ Topology topology = service.currentTopology();
+ assertNull("no topo expected", topology);
+ submitTopologyGraph();
+ validateEvents(TOPOLOGY_CHANGED);
+ topology = service.currentTopology();
+ assertTrue("should be latest", service.isLatest(topology));
+
+ submitTopologyGraph();
+ validateEvents(TOPOLOGY_CHANGED);
+ assertFalse("should be latest", service.isLatest(topology));
+ }
+
+ private void submitTopologyGraph() {
+ Set<Device> devices = of(device("a"), device("b"),
+ device("c"), device("d"),
+ device("e"), device("f"));
+ Set<Link> links = of(link("a", 1, "b", 1), link("b", 1, "a", 1),
+ link("b", 2, "c", 1), link("c", 1, "b", 2),
+ link("c", 2, "d", 1), link("d", 1, "c", 2),
+ link("d", 2, "a", 2), link("a", 2, "d", 2),
+ link("e", 1, "f", 1), link("f", 1, "e", 1));
+ GraphDescription data = new DefaultGraphDescription(4321L, devices, links);
+ providerService.topologyChanged(data, null);
+ }
+
+ @Test
+ public void clusters() {
+ submitTopologyGraph();
+ Topology topology = service.currentTopology();
+ assertNotNull("topo expected", topology);
+ assertEquals("wrong cluster count", 2, topology.clusterCount());
+ assertEquals("wrong device count", 6, topology.deviceCount());
+ assertEquals("wrong link count", 10, topology.linkCount());
+
+ assertEquals("wrong cluster count", 2, service.getClusters(topology).size());
+
+ TopologyCluster cluster = service.getCluster(topology, clusterId(0));
+ assertEquals("wrong device count", 4, cluster.deviceCount());
+ assertEquals("wrong device count", 4, service.getClusterDevices(topology, cluster).size());
+ assertEquals("wrong link count", 8, cluster.linkCount());
+ assertEquals("wrong link count", 8, service.getClusterLinks(topology, cluster).size());
+ }
+
+ @Test
+ public void structure() {
+ submitTopologyGraph();
+ Topology topology = service.currentTopology();
+
+ assertTrue("should be infrastructure point",
+ service.isInfrastructure(topology, new ConnectPoint(did("a"), portNumber(1))));
+ assertFalse("should not be infrastructure point",
+ service.isInfrastructure(topology, new ConnectPoint(did("a"), portNumber(3))));
+
+ assertTrue("should be broadcast point",
+ service.isBroadcastPoint(topology, new ConnectPoint(did("a"), portNumber(3))));
+ }
+
+ @Test
+ public void graph() {
+ submitTopologyGraph();
+ Topology topology = service.currentTopology();
+ TopologyGraph graph = service.getGraph(topology);
+ assertEquals("wrong vertex count", 6, graph.getVertexes().size());
+ assertEquals("wrong edge count", 10, graph.getEdges().size());
+ }
+
+ @Test
+ public void precomputedPath() {
+ submitTopologyGraph();
+ Topology topology = service.currentTopology();
+ Set<Path> paths = service.getPaths(topology, did("a"), did("c"));
+ assertEquals("wrong path count", 2, paths.size());
+ Path path = paths.iterator().next();
+ assertEquals("wrong path length", 2, path.links().size());
+ assertEquals("wrong path cost", 2, path.cost(), 0.01);
+ }
+
+ @Test
+ public void onDemandPath() {
+ submitTopologyGraph();
+ Topology topology = service.currentTopology();
+ LinkWeight weight = edge -> 3.3;
+
+ Set<Path> paths = service.getPaths(topology, did("a"), did("c"), weight);
+ assertEquals("wrong path count", 2, paths.size());
+ Path path = paths.iterator().next();
+ assertEquals("wrong path length", 2, path.links().size());
+ assertEquals("wrong path cost", 6.6, path.cost(), 0.01);
+ }
+
+ protected void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("wrong events received", types.length, listener.events.size());
+ for (Event event : listener.events) {
+ assertEquals("incorrect event type", types[i], event.type());
+ i++;
+ }
+ listener.events.clear();
+ }
+
+ private class TestProvider extends AbstractProvider implements TopologyProvider {
+ public TestProvider() {
+ super(PID);
+ }
+
+ @Override
+ public void triggerRecompute() {
+ }
+ }
+
+ private static class TestListener implements TopologyListener {
+ final List<TopologyEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(TopologyEvent event) {
+ events.add(event);
+ }
+ }
+
+}