aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/core
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/core')
-rw-r--r--framework/src/onos/core/api/pom.xml68
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java70
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java89
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationException.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationService.java70
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationState.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java108
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java125
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/app/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigEvent.java74
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigService.java74
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStore.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ConfigProperty.java279
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cfg/package-info.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java54
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterDefinitionService.java32
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEvent.java72
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEventListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterService.java68
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java84
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNode.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNodeToNodeId.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java103
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/Leadership.java164
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java107
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEventListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipService.java124
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/NodeId.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/cluster/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecContext.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecService.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/codec/JsonCodec.java115
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/codec/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java87
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationId.java36
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java59
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationRole.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/CoreService.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java149
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java81
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/GroupId.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlock.java102
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlockStore.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/IdGenerator.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/MetricsHelper.java56
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/UnavailableIdException.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/Version.java144
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/core/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractEvent.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractListenerManager.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/DefaultEventSinkRegistry.java62
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/Event.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDeliveryService.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDispatcher.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/EventFilter.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/EventListener.java30
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSink.java36
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSinkRegistry.java60
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerRegistry.java99
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerService.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/event/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipEvent.java95
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipService.java101
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java125
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTerm.java71
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTermService.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/mastership/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractAnnotated.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractDescription.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractElement.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractModel.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotated.java30
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java145
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotations.java40
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationsUtil.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java44
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/ConnectPoint.java175
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultAnnotations.java254
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultDevice.java142
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultEdgeLink.java96
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultHost.java117
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultLink.java131
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPath.java105
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPort.java127
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Description.java26
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Device.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/DeviceId.java99
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/EdgeLink.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Element.java30
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/ElementId.java22
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/GridType.java29
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Host.java68
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/HostId.java129
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/HostLocation.java67
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/IndexedLambda.java71
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/IpElementId.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Lambda.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Link.java114
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/LinkKey.java110
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/MastershipRole.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/MutableAnnotations.java40
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/NetTools.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/NetworkResource.java22
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/OchPort.java115
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignal.java176
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignalType.java32
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/OduCltPort.java97
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/OduSignalType.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/OmsPort.java131
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Path.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Port.java100
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/PortNumber.java182
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/Provided.java32
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/SparseAnnotations.java44
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeConfig.java87
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeDescription.java46
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeName.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerConfig.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultBridgeDescription.java87
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultNextGroup.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultTunnelDescription.java87
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/IpTunnelEndPoint.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/MplsQuery.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/NextGroup.java30
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/Pipeliner.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PipelinerContext.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortAdmin.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortConfig.java40
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueConfig.java56
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueInfo.java56
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelConfig.java55
-rwxr-xr-xframework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelDescription.java86
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java28
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelName.java79
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/VlanQuery.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/package-info.java21
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/Config.java312
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigApplyDelegate.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java122
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigOperator.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java92
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigRegistry.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java146
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java130
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/SubjectFactory.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/AllowedEntityConfig.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java70
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java130
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java27
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java90
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java175
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/SubjectFactories.java93
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/config/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultDeviceDescription.java145
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortDescription.java120
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java346
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceClockService.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceDescription.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceEvent.java141
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProvider.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderService.java82
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceService.java132
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStore.java170
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OchPortDescription.java111
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OduCltPortDescription.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OmsPortDescription.java109
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortDescription.java56
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java100
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/device/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractBehaviour.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractHandlerBehaviour.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Behaviour.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriver.java214
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverData.java100
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverHandler.java67
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProvider.java86
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProviderService.java23
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Driver.java132
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverAdminService.java46
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverConnect.java36
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverData.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverHandler.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverProvider.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverResolver.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverService.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/HandlerBehaviour.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/XmlDriverLoader.java176
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/package-info.java68
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortEvent.java59
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortService.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperation.java128
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationEntry.java90
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationResult.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/CompletedBatchOperation.java96
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowEntry.java137
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowRule.java390
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficSelector.java365
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java479
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowEntry.java102
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowId.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java265
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.java116
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java54
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java59
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleEvent.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleExtPayLoad.java67
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperation.java67
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperations.java181
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperationsContext.java28
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java52
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleService.java107
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java96
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/StoredFlowEntry.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficSelector.java420
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java431
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/Treatment.java36
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criteria.java527
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criterion.java181
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthTypeCriterion.java89
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPDscpCriterion.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPEcnCriterion.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPProtocolCriterion.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6ExthdrFlagsCriterion.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6FlowLabelCriterion.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDLinkLayerAddressCriterion.java81
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDTargetAddressCriterion.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpCodeCriterion.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpTypeCriterion.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6CodeCriterion.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6TypeCriterion.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/LambdaCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MetadataCriterion.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsBosCriterion.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsCriterion.java67
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalCriterion.java81
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalTypeCriterion.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OpticalSignalTypeCriterion.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/PortCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/SctpPortCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TcpPortCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TunnelIdCriterion.java74
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/UdpPortCriterion.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanIdCriterion.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanPcpCriterion.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instruction.java84
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java742
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L0ModificationInstruction.java139
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L2ModificationInstruction.java517
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L3ModificationInstruction.java231
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L4ModificationInstruction.java114
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/package-info.java26
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java239
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultForwardingObjective.java241
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java222
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java158
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveService.java65
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStore.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStoreDelegate.java26
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java158
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java167
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java134
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveContext.java47
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveError.java60
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveEvent.java64
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/package-info.java22
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java229
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java252
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java204
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupKey.java55
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/Group.java99
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java80
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupDescription.java91
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java99
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupKey.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java181
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperations.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProvider.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java47
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupService.java139
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStore.java175
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/group/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java122
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostAdminService.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostDescription.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostEvent.java73
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProvider.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostService.java146
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStore.java167
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/InterfaceIpAddress.java192
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/PortAddresses.java127
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/host/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java187
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Constraint.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java110
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/HostToHostIntent.java194
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Intent.java218
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentBatchDelegate.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentClockService.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java44
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentData.java327
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentEvent.java146
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentException.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentExtensionService.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentId.java87
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentListener.java26
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentOperation.java124
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentService.java123
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentState.java117
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStore.java143
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStoreDelegate.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Key.java163
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java241
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsIntent.java261
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsPathIntent.java167
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java223
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalCircuitIntent.java219
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalConnectivityIntent.java223
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalPathIntent.java234
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEvent.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEventListener.java26
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionService.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java202
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java215
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java219
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/TwoWayP2PIntent.java195
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AnnotationConstraint.java113
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AsymmetricPathConstraint.java64
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BandwidthConstraint.java96
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BooleanConstraint.java64
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LambdaConstraint.java91
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LatencyConstraint.java93
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LinkTypeConstraint.java108
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/ObstacleConstraint.java92
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/PartialFailureConstraint.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/WaypointConstraint.java117
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/package-info.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/DefaultLinkDescription.java73
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkAdminService.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkDescription.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkEvent.java68
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProvider.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderService.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkService.java116
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStore.java117
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/link/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Band.java133
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/BandEntry.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultBand.java136
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java233
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterRequest.java171
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Meter.java179
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterContext.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEntry.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEvent.java62
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterFailReason.java89
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterId.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperation.java88
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperations.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProvider.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderRegistry.java27
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java49
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java147
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterService.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterState.java43
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java90
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreResult.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAdminService.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAllocation.java94
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceConsumer.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java146
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceService.java155
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceStore.java90
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/package-info.java21
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultInboundPacket.java91
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java89
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketContext.java95
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java84
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/InboundPacket.java50
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketContext.java73
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketEvent.java56
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketPriority.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProcessor.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProvider.java32
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderService.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java47
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketService.java79
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStore.java59
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/package-info.java21
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractListenerProviderRegistry.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProvider.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java111
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderService.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/Provider.java30
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderId.java135
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderRegistry.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderService.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java64
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java45
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStoreDelegate.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocation.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocationException.java36
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceException.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceId.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceRequest.java32
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceType.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceService.java85
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceStore.java89
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResource.java72
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceAllocation.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceRequest.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceAllocations.java112
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceRequest.java186
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResource.java93
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceAllocation.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceRequest.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResource.java22
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceAllocations.java59
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceEvent.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceRequest.java93
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceService.java95
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStore.java73
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResources.java63
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabel.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceAllocation.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceRequest.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/package-info.java21
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/DefaultLoad.java111
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/Load.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticService.java85
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticStore.java65
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/ClusterId.java76
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java141
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyCluster.java97
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyEdge.java84
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyVertex.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java57
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/LinkWeight.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/PathService.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/Topology.java71
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyCluster.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEdge.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEvent.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyGraph.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyListener.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProvider.java30
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderRegistry.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderService.java37
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyService.java135
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStore.java144
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStoreDelegate.java24
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyVertex.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractApiDocRegistrator.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractInjectionResource.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractWebResource.java98
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocProvider.java98
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocService.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/rest/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/security/AppGuard.java38
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/security/AppPermission.java110
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/security/Permission.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityAdminService.java77
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityUtil.java82
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/security/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/AbstractStore.java72
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/Store.java51
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/StoreDelegate.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/Timestamp.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterCommunicationService.java166
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessage.java160
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessageHandler.java28
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/Endpoint.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessageSubject.java68
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessagingService.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncAtomicCounter.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java283
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounter.java59
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounterBuilder.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValue.java69
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueBuilder.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEvent.java109
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEventListener.java28
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMap.java291
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapBuilder.java143
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapException.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DatabaseUpdate.java220
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueue.java62
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueueBuilder.java79
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSet.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSetBuilder.java132
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMap.java207
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapBuilder.java187
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapEvent.java124
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapListener.java29
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/LogicalClockService.java35
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEvent.java135
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEventListener.java28
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapInfo.java47
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MultiValuedTimestamp.java111
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/PartitionInfo.java81
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Serializer.java81
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEvent.java113
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEventListener.java28
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageAdminService.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageException.java48
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageService.java83
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Transaction.java102
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContext.java78
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContextBuilder.java47
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionException.java54
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionalMap.java93
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Versioned.java138
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/WallClockTimestamp.java85
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/store/service/package-info.java21
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java143
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java142
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java200
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java207
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java122
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java165
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java46
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java304
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java111
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java52
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java40
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java72
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java43
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java104
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java90
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java70
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java190
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java147
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java27
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java252
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java353
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java129
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java160
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java159
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java21
-rw-r--r--framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/flow/doc-files/flow-design.pngbin0 -> 29150 bytes
-rw-r--r--framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-design.pngbin0 -> 32496 bytes
-rw-r--r--framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-states.pngbin0 -> 99143 bytes
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/TestApplicationId.java48
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/VersionTest.java83
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java78
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java55
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java36
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java60
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java78
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java66
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ComponentConfigAdapter.java53
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ConfigPropertyTest.java97
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java64
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ControllerNodeToNodeIdTest.java59
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipEventTest.java76
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipServiceAdapter.java87
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipTest.java75
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java60
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/codec/JsonCodecTest.java99
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java43
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/core/CoreServiceAdapter.java54
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java63
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultGroupIdTest.java41
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java36
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/AbstractEventTest.java79
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/BrokenListener.java28
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/DefaultEventSinkRegistryTest.java71
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/EventDeliveryServiceAdapter.java60
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/ListenerRegistryTest.java74
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/TestEvent.java34
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListener.java34
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListenerRegistry.java36
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipServiceAdapter.java67
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipTermTest.java57
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java110
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultAnnotationsTest.java100
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultDeviceTest.java79
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultEdgeLinkTest.java90
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultHostTest.java51
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultLinkTest.java65
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultPortTest.java70
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/DeviceIdTest.java37
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/HostIdTest.java44
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/IndexedLambdaTest.java35
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/LinkKeyTest.java129
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/NetTestTools.java138
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/OchSignalTest.java40
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/PortNumberTest.java43
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/TestDeviceParams.java55
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigRegistryAdapter.java42
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java90
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultDeviceDescriptionTest.java54
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultPortStatisticsTest.java126
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceClockServiceAdapter.java21
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceEventTest.java63
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java110
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverDataTest.java78
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverHandlerTest.java55
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverProviderTest.java49
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverTest.java89
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviour.java22
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourImpl.java22
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourNoConstructorImpl.java26
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwo.java22
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwoImpl.java22
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/XmlDriverLoaderTest.java80
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/BatchOperationTest.java153
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowEntryTest.java161
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowRuleTest.java161
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficSelectorTest.java295
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficTreatmentTest.java124
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowIdTest.java65
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java60
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java63
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleEventTest.java77
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleExtPayLoadTest.java36
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java75
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/criteria/CriteriaTest.java1138
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java725
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/flowobjective/ObjectiveTest.java313
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupDescriptionTest.java96
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupTest.java97
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupBucketTest.java133
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupOperationTest.java89
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/host/DefaultHostDecriptionTest.java57
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostEventTest.java72
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostServiceAdapter.java102
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/host/InterfaceIpAddressTest.java246
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/host/PortAddressesTest.java113
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/AbstractIntentTest.java35
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java47
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java268
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/HostToHostIntentTest.java126
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentDataTest.java179
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentExceptionTest.java48
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentIdTest.java95
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java89
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceTest.java252
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTest.java52
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java496
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/KeyTest.java121
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/LinkCollectionIntentTest.java209
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MockIdGenerator.java32
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsIntentTest.java117
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsPathIntentTest.java110
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MultiPointToSinglePointIntentTest.java66
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalConnectivityIntentTest.java35
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalPathIntentTest.java97
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PartitionServiceAdapter.java45
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PathIntentTest.java115
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java66
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java66
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestInstallableIntent.java53
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestIntent.java47
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassInstallableIntent.java37
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassIntent.java37
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestTools.java141
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestableIntentService.java27
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TwoWayP2PIntentTest.java100
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/AnnotationConstraintTest.java97
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ConstraintObjectsTest.java135
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/LatencyConstraintTest.java124
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ObstacleConstraintTest.java102
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/WaypointConstraintTest.java104
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/link/DefaultLinkDescriptionTest.java45
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkEventTest.java56
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkServiceAdapter.java97
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/DefaultMeterTest.java99
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java128
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourceAllocationTest.java50
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java82
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultInboundPacketTest.java81
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java82
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultPacketContextTest.java112
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java44
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderRegistryTest.java108
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderTest.java33
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/ProviderIdTest.java35
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/TestProvider.java32
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/resource/MplsObjectsTest.java89
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/DefaultLoadTest.java92
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/StatisticServiceAdapter.java61
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/ClusterIdTest.java41
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultGraphDescriptionTest.java60
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyClusterTest.java54
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyEdgeTest.java70
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyVertexTest.java44
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java92
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterCommunicationServiceAdapter.java88
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterMessageTest.java82
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/EndpointTest.java68
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/MessageSubjectTest.java64
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/AtomicValueEventTest.java71
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/ConsistentMapAdapter.java149
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/DatabaseUpdateTest.java127
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapAdapter.java111
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapEventTest.java82
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MapEventTest.java61
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MultiValuedTimestampTest.java94
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/SetEventTest.java75
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/StorageServiceAdapter.java56
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestAtomicCounter.java85
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestConsistentMap.java287
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestEventuallyConsistentMap.java238
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestStorageService.java55
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/VersionedTest.java84
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/store/service/WallClockTimestampTest.java66
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionServiceAdapter.java41
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java140
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java338
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java45
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellComparatorTest.java59
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellFormatterTest.java52
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AppIdFormatterTest.java50
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/ConnectPointFormatterTest.java45
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellComparatorTest.java147
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellFormatterTest.java71
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/EnumFormatterTest.java60
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HexFormatterTest.java57
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HostLocationFormatterTest.java46
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/TimeFormatterTest.java52
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkMapTest.java76
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java62
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java98
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ButtonIdTest.java56
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java91
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/LinkHighlightTest.java116
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ModTest.java62
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/NodeSelectionTest.java349
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java238
-rw-r--r--framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java72
-rw-r--r--framework/src/onos/core/api/src/test/resources/css.html2
-rw-r--r--framework/src/onos/core/api/src/test/resources/custom/css.html1
-rw-r--r--framework/src/onos/core/api/src/test/resources/custom/js.html1
-rw-r--r--framework/src/onos/core/api/src/test/resources/js.html2
-rw-r--r--framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.1.xml33
-rw-r--r--framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.bad.xml22
-rw-r--r--framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noclass.xml22
-rw-r--r--framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noconstructor.xml22
-rw-r--r--framework/src/onos/core/common/pom.xml58
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java64
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java49
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java49
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java132
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java74
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java118
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java63
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java57
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java78
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java225
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java449
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java235
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java93
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java78
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java201
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java396
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java243
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java58
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java70
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java94
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java64
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java79
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java55
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java39
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java70
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java73
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java112
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java80
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java45
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java47
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java80
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java160
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java41
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java41
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java71
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java76
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java20
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java502
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java43
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java432
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java20
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java21
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java71
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java202
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java445
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java609
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java59
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java65
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java118
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java55
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java122
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java546
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java87
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java61
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java120
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java65
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java247
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java438
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java288
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java512
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java83
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java54
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java47
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java64
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java66
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java141
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java157
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java48
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java55
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java70
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java170
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java154
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java139
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java62
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java691
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java530
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java327
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java717
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java482
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java293
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java48
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java212
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java135
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java286
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java307
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java366
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java542
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java388
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java184
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java64
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java211
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java157
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java83
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java21
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json5
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json3
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json19
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json5
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json38
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json44
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json39
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json20
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json12
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml29
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zipbin0 -> 1450 bytes
-rw-r--r--framework/src/onos/core/net/pom.xml98
-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
-rw-r--r--framework/src/onos/core/pom.xml85
-rw-r--r--framework/src/onos/core/security/pom.xml65
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/DefaultPolicyBuilder.java433
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/SecurityModeManager.java289
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/package-info.java20
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/DistributedSecurityModeStore.java315
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityInfo.java41
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeEvent.java48
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeListener.java25
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeState.java43
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStore.java104
-rw-r--r--framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStoreDelegate.java25
-rw-r--r--framework/src/onos/core/store/dist/pom.xml114
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java429
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/GossipComponentConfigStore.java120
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.java58
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java179
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java63
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterManagementMessageSubjects.java26
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEvent.java41
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEventType.java24
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterNodesDelegate.java54
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java280
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/NodeInfo.java118
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/PhiAccrualFailureDetector.java119
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManager.java261
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java40
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java72
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java289
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/AsyncCachingConsistentMap.java71
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CommitResponse.java61
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/ConsistentMapBackedJavaMap.java145
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CopycatCommunicationProtocol.java134
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Database.java106
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseConfig.java157
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java108
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java74
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java455
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabasePartitioner.java45
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseProxy.java224
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseSerializer.java103
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseState.java114
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncAtomicCounter.java84
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncConsistentMap.java465
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounter.java82
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounterBuilder.java77
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValue.java138
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValueBuilder.java71
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMap.java204
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMapBuilder.java141
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabase.java243
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabaseState.java368
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueue.java129
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueueBuilder.java81
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSet.java234
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSetBuilder.java93
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransaction.java70
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java116
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContextBuilder.java50
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionalMap.java204
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DistributedLeadershipManager.java605
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Match.java129
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/MeteringAgent.java134
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/PartitionedDatabase.java386
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Partitioner.java33
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Result.java121
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleKeyHashPartitioner.java38
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleTableHashPartitioner.java39
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/StateMachineUpdate.java91
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/TransactionManager.java126
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/UpdateResult.java85
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/package-info.java21
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdEvent.java34
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdStoreDelegate.java24
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentApplicationIdStore.java154
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentIdBlockStore.java64
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/LogicalClockManager.java51
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyAdvertisement.java72
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyRequest.java61
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceClockManager.java82
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceDescriptions.java134
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceFragmentId.java69
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceInjectedEvent.java49
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceKey.java70
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/ECDeviceStore.java784
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java1670
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStoreMessageSubjects.java41
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEvent.java71
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEventSerializer.java60
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEvent.java64
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEventSerializer.java53
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceRemovedEvent.java64
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEvent.java73
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEventSerializer.java62
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEvent.java71
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEventSerializer.java58
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortFragmentId.java76
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortInjectedEvent.java50
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortKey.java79
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/AntiEntropyAdvertisement.java71
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapBuilderImpl.java161
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java678
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java103
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapValue.java158
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/PersistentStore.java47
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/UpdateEntry.java80
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/package-info.java21
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfo.java85
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEvent.java64
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEventListener.java26
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoService.java48
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/FlowStoreMessageSubjects.java43
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java789
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/ReplicaInfoManager.java123
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/package-info.java21
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStore.java102
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java1304
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessage.java184
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessageSubjects.java28
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java19
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/ECHostStore.java267
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/LogicalTimestamp.java68
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/MastershipBasedTimestamp.java117
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/Timestamped.java119
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java334
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionId.java68
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionManager.java243
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java390
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStore.java903
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStoreMessageSubjects.java35
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkEvent.java61
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkRemovedEvent.java64
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkAntiEntropyAdvertisement.java63
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkFragmentId.java77
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkInjectedEvent.java38
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/Provided.java68
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java419
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValue.java179
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValueSerializer.java67
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java349
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/DistributedPacketStore.java207
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java174
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java225
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentLinkResourceStore.java503
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/ClusterMessageSerializer.java53
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/DistributedStoreSerializers.java42
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MastershipBasedTimestampSerializer.java51
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MessageSubjectSerializer.java46
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/package-info.java22
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/DistributedStatisticStore.java317
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/StatisticStoreMessageSubjects.java30
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java254
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/PathKey.java55
-rw-r--r--framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/package-info.java20
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/StaticClusterService.java55
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManagerTest.java142
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/config/impl/DistributedNetworkConfigStoreTest.java127
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/MatchTest.java52
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/ResultTest.java42
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/UpdateResultTest.java84
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/DeviceFragmentIdTest.java48
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/GossipDeviceStoreTest.java917
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/PortFragmentIdTest.java61
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/EventuallyConsistentMapImplTest.java909
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/MapValueTest.java79
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ReplicaInfoManagerTest.java167
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStoreTest.java63
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java420
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/MastershipBasedTimestampTest.java110
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/TimestampedTest.java109
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/GossipIntentStoreTest.java234
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java329
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/GossipLinkStoreTest.java632
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/LinkFragmentIdTest.java63
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/DistributedMastershipStoreTest.java286
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/RoleValueTest.java46
-rw-r--r--framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/resource/impl/HazelcastLinkResourceStoreTest.java227
-rw-r--r--framework/src/onos/core/store/pom.xml54
-rw-r--r--framework/src/onos/core/store/serializers/pom.xml50
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/AnnotationsSerializer.java32
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ArraysAsListSerializer.java49
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ConnectPointSerializer.java51
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultApplicationIdSerializer.java49
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultLinkSerializer.java61
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultOutboundPacketSerializer.java58
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultPortSerializer.java65
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DeviceIdSerializer.java48
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/HostLocationSerializer.java55
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableListSerializer.java55
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableMapSerializer.java58
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableSetSerializer.java55
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4AddressSerializer.java52
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4PrefixSerializer.java57
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6AddressSerializer.java52
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6PrefixSerializer.java57
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpAddressSerializer.java58
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpPrefixSerializer.java64
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java479
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoSerializer.java86
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/LinkKeySerializer.java51
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MacAddressSerializer.java47
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MastershipTermSerializer.java51
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/NodeIdSerializer.java48
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/PortNumberSerializer.java56
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ProviderIdSerializer.java53
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/StoreSerializer.java78
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/URISerializer.java46
-rw-r--r--framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/package-info.java20
-rw-r--r--framework/src/onos/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java470
1309 files changed, 146141 insertions, 0 deletions
diff --git a/framework/src/onos/core/api/pom.xml b/framework/src/onos/core/api/pom.xml
new file mode 100644
index 00000000..7cfb4acc
--- /dev/null
+++ b/framework/src/onos/core/api/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-api</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS network control API</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-configuration</groupId>
+ <artifactId>commons-configuration</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-osgi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java
new file mode 100644
index 00000000..3713e218
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Service for managing network control applications.
+ */
+public interface ApplicationAdminService extends ApplicationService {
+
+ /**
+ * Installs the application contained in the specified application archive
+ * input stream. This can be either a ZIP stream containing a compressed
+ * application archive or a plain XML stream containing just the
+ * {@code app.xml} application descriptor file.
+ *
+ * @param appDescStream application descriptor input stream
+ * @return installed application descriptor
+ * @throws org.onosproject.app.ApplicationException if unable to read the app archive stream
+ */
+ Application install(InputStream appDescStream);
+
+ /**
+ * Uninstalls the specified application.
+ *
+ * @param appId application identifier
+ */
+ void uninstall(ApplicationId appId);
+
+ /**
+ * Activates the specified application.
+ *
+ * @param appId application identifier
+ */
+ void activate(ApplicationId appId);
+
+ /**
+ * Deactivates the specified application.
+ *
+ * @param appId application identifier
+ */
+ void deactivate(ApplicationId appId);
+
+ /**
+ * Updates the permissions granted to the applications.
+ *
+ * @param appId application identifier
+ * @param permissions set of granted permissions
+ */
+ void setPermissions(ApplicationId appId, Set<Permission> permissions);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
new file mode 100644
index 00000000..2561280b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.Version;
+import org.onosproject.security.Permission;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Description of a network control/management application.
+ */
+public interface ApplicationDescription {
+
+ /**
+ * Returns the application name id.
+ *
+ * @return application identifier
+ */
+ String name();
+
+ /**
+ * Returns the application version.
+ *
+ * @return application version
+ */
+ Version version();
+
+ /**
+ * Returns description of the application.
+ *
+ * @return application description text
+ */
+ String description();
+
+ /**
+ * Returns the name of the application origin, group or company.
+ *
+ * @return application origin
+ */
+ String origin();
+
+ /**
+ * Returns the role of the application.
+ *
+ * @return application role
+ */
+ ApplicationRole role();
+
+ /**
+ * Returns the permissions requested by the application.
+ *
+ * @return requested permissions
+ */
+ Set<Permission> permissions();
+
+ /**
+ * Returns the feature repository URI. Null value signifies that the
+ * application did not provide its own features repository.
+ *
+ * @return optional feature repo URL
+ */
+ Optional<URI> featuresRepo();
+
+ /**
+ * Returns the list of features comprising the application. At least one
+ * feature must be given.
+ *
+ * @return application features
+ */
+ List<String> features();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java
new file mode 100644
index 00000000..5bf1323d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import org.onosproject.core.Application;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes application lifecycle event.
+ */
+public class ApplicationEvent extends AbstractEvent<ApplicationEvent.Type, Application> {
+
+ public enum Type {
+ /**
+ * Signifies that an application has been installed.
+ */
+ APP_INSTALLED,
+
+ /**
+ * Signifies that an application has been activated.
+ */
+ APP_ACTIVATED,
+
+ /**
+ * Signifies that an application has been deactivated.
+ */
+ APP_DEACTIVATED,
+
+ /**
+ * Signifies that an application has been uninstalled.
+ */
+ APP_UNINSTALLED,
+
+ /**
+ * Signifies that application granted permissions have changed.
+ */
+ APP_PERMISSIONS_CHANGED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified app and the
+ * current time.
+ *
+ * @param type app event type
+ * @param app event app subject
+ */
+ public ApplicationEvent(Type type, Application app) {
+ super(type, app);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified app and time.
+ *
+ * @param type app event type
+ * @param app event app subject
+ * @param time occurrence time
+ */
+ public ApplicationEvent(Type type, Application app, long time) {
+ super(type, app, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationException.java
new file mode 100644
index 00000000..2888c70b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationException.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.app;
+
+/**
+ * Represents class of errors related to application management.
+ */
+public class ApplicationException extends RuntimeException {
+
+ private static final long serialVersionUID = -2287403908433720122L;
+
+ /**
+ * Constructs an exception with no message and no underlying cause.
+ */
+ public ApplicationException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message describing the specific nature of the error
+ */
+ public ApplicationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and the underlying cause.
+ *
+ * @param message the message describing the specific nature of the error
+ * @param cause the underlying cause of this error
+ */
+ public ApplicationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationListener.java
new file mode 100644
index 00000000..7a680572
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving application related events.
+ */
+public interface ApplicationListener extends EventListener<ApplicationEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationService.java
new file mode 100644
index 00000000..73dcc86c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationService.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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.security.Permission;
+
+import java.util.Set;
+
+/**
+ * Service for inspecting inventory of network control applications.
+ */
+public interface ApplicationService
+ extends ListenerService<ApplicationEvent, ApplicationListener> {
+
+ /**
+ * Returns the set of all installed applications.
+ *
+ * @return set of installed apps
+ */
+ Set<Application> getApplications();
+
+ /**
+ * Returns the registered id of the application with the given name.
+ *
+ * @param name application name
+ * @return registered application id
+ */
+ ApplicationId getId(String name);
+
+ /**
+ * Returns the application with the supplied application identifier.
+ *
+ * @param appId application identifier
+ * @return application descriptor
+ */
+ Application getApplication(ApplicationId appId);
+
+ /**
+ * Return the application state.
+ *
+ * @param appId application identifier
+ * @return application state
+ */
+ ApplicationState getState(ApplicationId appId);
+
+ /**
+ * Returns the permissions currently granted to the applications.
+ *
+ * @param appId application identifier
+ * @return set of granted permissions
+ */
+ Set<Permission> getPermissions(ApplicationId appId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationState.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationState.java
new file mode 100644
index 00000000..c480a0c7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationState.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Representation of an application state.
+ */
+public enum ApplicationState {
+
+ /**
+ * Indicates that application has been installed, but is not running.
+ */
+ INSTALLED,
+
+ /**
+ * Indicates that application is active.
+ */
+ ACTIVE
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
new file mode 100644
index 00000000..b3cdc43e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+import org.onosproject.store.Store;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Service for managing network control applications.
+ */
+public interface ApplicationStore extends Store<ApplicationEvent, ApplicationStoreDelegate> {
+
+ /**
+ * Returns the set of all installed applications.
+ *
+ * @return set of installed apps
+ */
+ Set<Application> getApplications();
+
+ /**
+ * Returns the registered id of the application with the given name.
+ *
+ * @param name application name
+ * @return registered application id
+ */
+ ApplicationId getId(String name);
+
+ /**
+ * Returns the application with the supplied application identifier.
+ *
+ * @param appId application identifier
+ * @return application descriptor
+ */
+ Application getApplication(ApplicationId appId);
+
+ /**
+ * Returns the current application state.
+ *
+ * @param appId application identifier
+ * @return application state
+ */
+ ApplicationState getState(ApplicationId appId);
+
+ /**
+ * Creates the application from the specified application descriptor
+ * input stream.
+ *
+ * @param appDescStream application archive input stream
+ * @return application descriptor
+ */
+ Application create(InputStream appDescStream);
+
+ /**
+ * Removes the specified application.
+ *
+ * @param appId application identifier
+ */
+ void remove(ApplicationId appId);
+
+ /**
+ * Mark the application as actived.
+ *
+ * @param appId application identifier
+ */
+ void activate(ApplicationId appId);
+
+ /**
+ * Mark the application as deactivated.
+ *
+ * @param appId application identifier
+ */
+ void deactivate(ApplicationId appId);
+
+ /**
+ * Returns the permissions granted to the applications.
+ *
+ * @param appId application identifier
+ * @return set of granted permissions
+ */
+ Set<Permission> getPermissions(ApplicationId appId);
+
+ /**
+ * Updates the permissions granted to the applications.
+ *
+ * @param appId application identifier
+ * @param permissions set of granted permissions
+ */
+ void setPermissions(ApplicationId appId, Set<Permission> permissions);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java
new file mode 100644
index 00000000..f339e685
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Application store delegate abstraction.
+ */
+public interface ApplicationStoreDelegate extends StoreDelegate<ApplicationEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
new file mode 100644
index 00000000..710d0f9c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.Version;
+import org.onosproject.security.Permission;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of network control/management application descriptor.
+ */
+public class DefaultApplicationDescription implements ApplicationDescription {
+
+ private final String name;
+ private final Version version;
+ private final String description;
+ private final String origin;
+ private final ApplicationRole role;
+ private final Set<Permission> permissions;
+ private final Optional<URI> featuresRepo;
+ private final List<String> features;
+
+ /**
+ * Creates a new application descriptor using the supplied data.
+ *
+ * @param name application name
+ * @param version application version
+ * @param description application description
+ * @param origin origin company
+ * @param role application role
+ * @param permissions requested permissions
+ * @param featuresRepo optional features repo URI
+ * @param features application features
+ */
+ public DefaultApplicationDescription(String name, Version version,
+ String description, String origin,
+ ApplicationRole role, Set<Permission> permissions,
+ URI featuresRepo, List<String> features) {
+ this.name = checkNotNull(name, "Name cannot be null");
+ this.version = checkNotNull(version, "Version cannot be null");
+ this.description = checkNotNull(description, "Description cannot be null");
+ this.origin = checkNotNull(origin, "Origin cannot be null");
+ this.role = checkNotNull(role, "Role cannot be null");
+ this.permissions = checkNotNull(permissions, "Permissions cannot be null");
+ this.featuresRepo = Optional.ofNullable(featuresRepo);
+ this.features = checkNotNull(features, "Features cannot be null");
+ checkArgument(!features.isEmpty(), "There must be at least one feature");
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Version version() {
+ return version;
+ }
+
+ @Override
+ public String description() {
+ return description;
+ }
+
+ @Override
+ public String origin() {
+ return origin;
+ }
+
+ @Override
+ public ApplicationRole role() {
+ return role;
+ }
+
+ @Override
+ public Set<Permission> permissions() {
+ return permissions;
+ }
+
+ @Override
+ public Optional<URI> featuresRepo() {
+ return featuresRepo;
+ }
+
+ @Override
+ public List<String> features() {
+ return features;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("name", name)
+ .add("version", version)
+ .add("description", description)
+ .add("origin", origin)
+ .add("role", role)
+ .add("permissions", permissions)
+ .add("featuresRepo", featuresRepo)
+ .add("features", features)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/package-info.java
new file mode 100644
index 00000000..f8e5465d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/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.
+ */
+
+/**
+ * Set of abstractions for managing network control applications.
+ */
+package org.onosproject.app; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigEvent.java
new file mode 100644
index 00000000..0f5a2ee5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigEvent.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes a component configuration event.
+ */
+public class ComponentConfigEvent extends AbstractEvent<ComponentConfigEvent.Type, String> {
+
+ private final String name;
+ private final String value;
+
+ public enum Type {
+ /**
+ * Signifies that a configuration property has set.
+ */
+ PROPERTY_SET,
+
+ /**
+ * Signifies that a configuration property has been unset.
+ */
+ PROPERTY_UNSET
+ }
+
+ /**
+ * Creates an event of a given type and for the specified app and the
+ * current time.
+ *
+ * @param type config property event type
+ * @param componentName component name event subject
+ * @param name config property name
+ * @param value config property value
+ */
+ public ComponentConfigEvent(Type type, String componentName,
+ String name, String value) {
+ super(type, componentName);
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * Returns the property name.
+ *
+ * @return property name
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the property value as a string.
+ *
+ * @return string value
+ */
+ public String value() {
+ return value;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigService.java
new file mode 100644
index 00000000..a311002f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigService.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+/**
+ * Service for tracking system-wide configurations for various software components.
+ */
+public interface ComponentConfigService {
+
+ /**
+ * Returns names of all components that have registered their
+ * configuration properties.
+ *
+ * @return set of component names
+ */
+ Set<String> getComponentNames();
+
+ /**
+ * Registers configuration properties for the specified component.
+ *
+ * @param componentClass class of configurable component
+ */
+ void registerProperties(Class<?> componentClass);
+
+ /**
+ * Unregisters configuration properties for the specified component.
+ *
+ * @param componentClass class of configurable component
+ * @param clear true indicates any settings should be cleared
+ */
+ void unregisterProperties(Class<?> componentClass, boolean clear);
+
+ /**
+ * Returns configuration properties of the named components.
+ *
+ * @param componentName component name
+ * @return set of configuration properties
+ */
+ Set<ConfigProperty> getProperties(String componentName);
+
+ /**
+ * Sets the value of the specified configuration property.
+ *
+ * @param componentName component name
+ * @param name property name
+ * @param value new property value
+ */
+ void setProperty(String componentName, String name, String value);
+
+ /**
+ * Clears the value of the specified configuration property thus making
+ * the property take on its default value.
+ *
+ * @param componentName component name
+ * @param name property name
+ */
+ void unsetProperty(String componentName, String name);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStore.java
new file mode 100644
index 00000000..05f58a4c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStore.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.onosproject.store.Store;
+
+/**
+ * Service for storing and distributing system-wide configurations for various
+ * software components.
+ */
+public interface ComponentConfigStore
+ extends Store<ComponentConfigEvent, ComponentConfigStoreDelegate> {
+
+ /**
+ * Sets the value of the specified configuration property.
+ *
+ * @param componentName component name
+ * @param name property name
+ * @param value new property value
+ */
+ void setProperty(String componentName, String name, String value);
+
+ /**
+ * Clears the value of the specified configuration property thus making
+ * the property take on its default value.
+ *
+ * @param componentName component name
+ * @param name property name
+ */
+ void unsetProperty(String componentName, String name);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStoreDelegate.java
new file mode 100644
index 00000000..da262789
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ComponentConfigStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Configuration property store delegate abstraction.
+ */
+public interface ComponentConfigStoreDelegate extends StoreDelegate<ComponentConfigEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ConfigProperty.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ConfigProperty.java
new file mode 100644
index 00000000..36cd22b5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/ConfigProperty.java
@@ -0,0 +1,279 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Component configuration property.
+ */
+public final class ConfigProperty {
+
+ private final String name;
+ private final Type type;
+ private final String value;
+ private final String defaultValue;
+ private final String description;
+ private final boolean isSet;
+
+ /**
+ * Representation of the type of property value.
+ */
+ public enum Type {
+ /**
+ * Indicates the value is a string.
+ */
+ STRING,
+
+ /**
+ * Indicates the value is an integer.
+ */
+ INTEGER,
+
+ /**
+ * Indicates the value is a long.
+ */
+ LONG,
+
+ /**
+ * Indicates the value is a float.
+ */
+ FLOAT,
+
+ /**
+ * Indicates the value is a double.
+ */
+ DOUBLE,
+
+ /**
+ * Indicates the value is a boolean.
+ */
+ BOOLEAN
+ }
+
+ /**
+ * Creates a new configuration property with its default value.
+ *
+ * @param name property name
+ * @param type value type
+ * @param defaultValue default value as a string
+ * @param description property description
+ * @return newly defined property
+ */
+ public static ConfigProperty defineProperty(String name, Type type,
+ String defaultValue,
+ String description) {
+ return new ConfigProperty(name, type, description, defaultValue, defaultValue, false);
+ }
+
+ /**
+ * Creates a new configuration property as a copy of an existing one, but
+ * with a new value.
+ *
+ * @param property property to be changed
+ * @param newValue new value as a string
+ * @return newly updated property
+ */
+ public static ConfigProperty setProperty(ConfigProperty property, String newValue) {
+ return new ConfigProperty(property.name, property.type, property.description,
+ property.defaultValue, newValue, true);
+ }
+
+ /**
+ * Creates a new configuration property as a copy of an existing one, but
+ * without a specific value, thus making it take its default value.
+ *
+ * @param property property to be reset
+ * @return newly reset property
+ */
+ public static ConfigProperty resetProperty(ConfigProperty property) {
+ return new ConfigProperty(property.name, property.type, property.description,
+ property.defaultValue, property.defaultValue, false);
+ }
+
+ /**
+ * Creates a new configuration property with its default value.
+ *
+ * @param name property name
+ * @param type value type
+ * @param defaultValue default value as a string
+ * @param description property description
+ * @param value property value
+ * @param isSet indicates whether the property is set or not
+ */
+ private ConfigProperty(String name, Type type, String description,
+ String defaultValue, String value, boolean isSet) {
+ this.name = checkNotNull(name, "Property name cannot be null");
+ this.type = checkNotNull(type, "Property type cannot be null");
+ this.description = checkNotNull(description, "Property description cannot be null");
+ this.defaultValue = defaultValue;
+ this.value = value;
+ this.isSet = isSet;
+ }
+
+ /**
+ * Returns the property name.
+ *
+ * @return property name
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the property type.
+ *
+ * @return property type
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the property description.
+ *
+ * @return string value
+ */
+ public String description() {
+ return description;
+ }
+
+ /**
+ * Returns the property default value as a string.
+ *
+ * @return string default value
+ */
+ public String defaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Returns the property value as a string.
+ *
+ * @return string value
+ */
+ public String value() {
+ return value;
+ }
+
+ /**
+ * Indicates whether the property is set or whether it assumes its
+ * default value.
+ *
+ * @return true if the property is set
+ */
+ public boolean isSet() {
+ return isSet;
+ }
+
+ /**
+ * Returns the property value as a string.
+ *
+ * @return string value
+ */
+ public String asString() {
+ return value;
+ }
+
+ /**
+ * Returns the property value as an integer.
+ *
+ * @return integer value
+ */
+ public int asInteger() {
+ checkState(type == Type.INTEGER, "Value is not an integer");
+ return Integer.parseInt(value);
+ }
+
+ /**
+ * Returns the property value as a long.
+ *
+ * @return long value
+ */
+ public long asLong() {
+ checkState(type == Type.INTEGER || type == Type.LONG, "Value is not a long or integer");
+ return Long.parseLong(value);
+ }
+
+ /**
+ * Returns the property value as a float.
+ *
+ * @return float value
+ */
+ public float asFloat() {
+ checkState(type == Type.FLOAT, "Value is not a float");
+ return Float.parseFloat(value);
+ }
+
+ /**
+ * Returns the property value as a double.
+ *
+ * @return double value
+ */
+ public double asDouble() {
+ checkState(type == Type.FLOAT || type == Type.DOUBLE, "Value is not a float or double");
+ return Double.parseDouble(value);
+ }
+
+ /**
+ * Returns the property value as a boolean.
+ *
+ * @return string value
+ */
+ public boolean asBoolean() {
+ checkState(type == Type.BOOLEAN, "Value is not a boolean");
+ return Boolean.parseBoolean(value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Equality is considered only on the basis of property name.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ConfigProperty) {
+ final ConfigProperty other = (ConfigProperty) obj;
+ return Objects.equals(this.name, other.name);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name)
+ .add("type", type)
+ .add("value", value)
+ .add("defaultValue", defaultValue)
+ .add("description", description)
+ .add("isSet", isSet)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/package-info.java
new file mode 100644
index 00000000..99fd5be4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cfg/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * Set of abstractions for centrally managing component configurations.
+ * Configuration properties are registered for a component resource which is
+ * auto-generated during the build process based on information specified in
+ * the @Property annotations. This provides an overall inventory of all
+ * supported component configurations.
+ */
+package org.onosproject.cfg; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
new file mode 100644
index 00000000..5f2b5fff
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import org.onlab.packet.IpAddress;
+
+import java.util.Set;
+
+/**
+ * Service for administering the cluster node membership.
+ */
+public interface ClusterAdminService {
+
+ /**
+ * Forms cluster configuration based on the specified set of node
+ * information.&nbsp; This method resets and restarts the controller
+ * instance.
+ *
+ * @param nodes set of nodes that form the cluster
+ * @param ipPrefix IP address prefix, e.g. 10.0.1.*
+ */
+ void formCluster(Set<ControllerNode> nodes, String ipPrefix);
+
+ /**
+ * Adds a new controller node to the cluster.
+ *
+ * @param nodeId controller node identifier
+ * @param ip node IP listen address
+ * @param tcpPort tcp listen port
+ * @return newly added node
+ */
+ ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort);
+
+ /**
+ * Removes the specified node from the cluster node list.
+ *
+ * @param nodeId controller node identifier
+ */
+ void removeNode(NodeId nodeId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterDefinitionService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterDefinitionService.java
new file mode 100644
index 00000000..dbe5f71c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterDefinitionService.java
@@ -0,0 +1,32 @@
+package org.onosproject.cluster;
+
+import java.util.Set;
+
+/**
+ * Service for obtaining the static definition of a controller cluster.
+ */
+public interface ClusterDefinitionService {
+
+ /**
+ * Returns the local controller node.
+ * @return local controller node
+ */
+ ControllerNode localNode();
+
+ /**
+ * Returns the set of seed nodes that should be used for discovering other members
+ * of the cluster.
+ * @return set of seed controller nodes
+ */
+ Set<ControllerNode> seedNodes();
+
+ /**
+ * Forms cluster configuration based on the specified set of node
+ * information. Assumes subsequent restart for the new configuration to
+ * take hold.
+ *
+ * @param nodes set of nodes that form the cluster
+ * @param ipPrefix IP address prefix, e.g. 10.0.1.*
+ */
+ void formCluster(Set<ControllerNode> nodes, String ipPrefix);
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEvent.java
new file mode 100644
index 00000000..7bdc1d7d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEvent.java
@@ -0,0 +1,72 @@
+/*
+ * 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.cluster;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes cluster-related event.
+ */
+public class ClusterEvent extends AbstractEvent<ClusterEvent.Type, ControllerNode> {
+
+ /**
+ * Type of cluster-related events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a new cluster instance has been administratively added.
+ */
+ INSTANCE_ADDED,
+
+ /**
+ * Signifies that a cluster instance has been administratively removed.
+ */
+ INSTANCE_REMOVED,
+
+ /**
+ * Signifies that a cluster instance became active.
+ */
+ INSTANCE_ACTIVATED,
+
+ /**
+ * Signifies that a cluster instance became inactive.
+ */
+ INSTANCE_DEACTIVATED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified instance and the
+ * current time.
+ *
+ * @param type cluster event type
+ * @param instance cluster device subject
+ */
+ public ClusterEvent(Type type, ControllerNode instance) {
+ super(type, instance);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified device and time.
+ *
+ * @param type device event type
+ * @param instance event device subject
+ * @param time occurrence time
+ */
+ public ClusterEvent(Type type, ControllerNode instance, long time) {
+ super(type, instance, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEventListener.java
new file mode 100644
index 00000000..79ff06ec
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterEventListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.cluster;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving device cluster-related events.
+ */
+public interface ClusterEventListener extends EventListener<ClusterEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
new file mode 100644
index 00000000..015a6482
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.joda.time.DateTime;
+import org.onosproject.event.ListenerService;
+
+/**
+ * Service for obtaining information about the individual nodes within
+ * the controller cluster.
+ */
+public interface ClusterService
+ extends ListenerService<ClusterEvent, ClusterEventListener> {
+
+ /**
+ * Returns the local controller node.
+ *
+ * @return local controller node
+ */
+ ControllerNode getLocalNode();
+
+ /**
+ * Returns the set of current cluster members.
+ *
+ * @return set of cluster members
+ */
+ Set<ControllerNode> getNodes();
+
+ /**
+ * Returns the specified controller node.
+ *
+ * @param nodeId controller node identifier
+ * @return controller node
+ */
+ ControllerNode getNode(NodeId nodeId);
+
+ /**
+ * Returns the availability state of the specified controller node.
+ *
+ * @param nodeId controller node identifier
+ * @return availability state
+ */
+ ControllerNode.State getState(NodeId nodeId);
+
+ /**
+ * Returns the system time when the availability state was last updated.
+ *
+ * @param nodeId controller node identifier
+ * @return system time when the availability state was last updated.
+ */
+ DateTime getLastUpdated(NodeId nodeId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
new file mode 100644
index 00000000..0481d510
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStore.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.cluster;
+
+import org.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Manages inventory of controller cluster nodes; not intended for direct use.
+ */
+public interface ClusterStore extends Store<ClusterEvent, ClusterStoreDelegate> {
+
+ /**
+ * Returns the local controller node.
+ *
+ * @return local controller instance
+ */
+ ControllerNode getLocalNode();
+
+ /**
+ * Returns the set of current cluster members.
+ *
+ * @return set of cluster members
+ */
+ Set<ControllerNode> getNodes();
+
+ /**
+ * Returns the specified controller node.
+ *
+ * @param nodeId controller instance identifier
+ * @return controller instance
+ */
+ ControllerNode getNode(NodeId nodeId);
+
+ /**
+ * Returns the availability state of the specified controller node.
+ *
+ * @param nodeId controller instance identifier
+ * @return availability state
+ */
+ ControllerNode.State getState(NodeId nodeId);
+
+ /**
+ * Returns the system when the availability state was last updated.
+ *
+ * @param nodeId controller node identifier
+ * @return system time when the availability state was last updated.
+ */
+ DateTime getLastUpdated(NodeId nodeId);
+
+ /**
+ * Adds a new controller node to the cluster.
+ *
+ * @param nodeId controller node identifier
+ * @param ip node IP listen address
+ * @param tcpPort tcp listen port
+ * @return newly added node
+ */
+ ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort);
+
+ /**
+ * Removes the specified node from the inventory of cluster nodes.
+ *
+ * @param nodeId controller instance identifier
+ */
+ void removeNode(NodeId nodeId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStoreDelegate.java
new file mode 100644
index 00000000..50d44305
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ClusterStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.cluster;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Cluster store delegate abstraction.
+ */
+public interface ClusterStoreDelegate extends StoreDelegate<ClusterEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNode.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNode.java
new file mode 100644
index 00000000..3cfc9367
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNode.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cluster;
+
+import org.onlab.packet.IpAddress;
+
+/**
+ * Represents a controller instance as a member in a cluster.
+ */
+public interface ControllerNode {
+
+ /** Represents the operational state of the instance. */
+ public enum State {
+ /**
+ * Signifies that the instance is active and operating normally.
+ */
+ ACTIVE,
+
+ /**
+ * Signifies that the instance is inactive, which means either down or
+ * up, but not operational.
+ */
+ INACTIVE
+ }
+
+ /**
+ * Returns the instance identifier.
+ *
+ * @return instance identifier
+ */
+ NodeId id();
+
+ /**
+ * Returns the IP address of the controller instance.
+ *
+ * @return IP address
+ */
+ IpAddress ip();
+
+
+ /**
+ * Returns the TCP port on which the node listens for connections.
+ *
+ * @return TCP port
+ */
+ int tcpPort();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNodeToNodeId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNodeToNodeId.java
new file mode 100644
index 00000000..4cde8b29
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/ControllerNodeToNodeId.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cluster;
+
+import com.google.common.base.Function;
+
+/**
+ * Function to convert ControllerNode to NodeId.
+ */
+public final class ControllerNodeToNodeId
+ implements Function<ControllerNode, NodeId> {
+
+ private static final ControllerNodeToNodeId INSTANCE = new ControllerNodeToNodeId();
+
+ @Override
+ public NodeId apply(ControllerNode input) {
+ if (input == null) {
+ return null;
+ } else {
+ return input.id();
+ }
+ }
+
+ /**
+ * Returns a Function to convert ControllerNode to NodeId.
+ *
+ * @return ControllerNodeToNodeId instance.
+ */
+ public static ControllerNodeToNodeId toNodeId() {
+ return INSTANCE;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
new file mode 100644
index 00000000..5f3e0e19
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import org.onlab.packet.IpAddress;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of a controller instance descriptor.
+ */
+public class DefaultControllerNode implements ControllerNode {
+
+ public static final int DEFAULT_PORT = 9876;
+
+ private final NodeId id;
+ private final IpAddress ip;
+ private final int tcpPort;
+
+ // For serialization
+ private DefaultControllerNode() {
+ this.id = null;
+ this.ip = null;
+ this.tcpPort = 0;
+ }
+
+ /**
+ * Creates a new instance with the specified id and IP address.
+ *
+ * @param id instance identifier
+ * @param ip instance IP address
+ */
+ public DefaultControllerNode(NodeId id, IpAddress ip) {
+ this(id, ip, DEFAULT_PORT);
+ }
+
+ /**
+ * Creates a new instance with the specified id and IP address and TCP port.
+ *
+ * @param id instance identifier
+ * @param ip instance IP address
+ * @param tcpPort TCP port
+ */
+ public DefaultControllerNode(NodeId id, IpAddress ip, int tcpPort) {
+ this.id = id;
+ this.ip = ip;
+ this.tcpPort = tcpPort;
+ }
+
+ @Override
+ public NodeId id() {
+ return id;
+ }
+
+ @Override
+ public IpAddress ip() {
+ return ip;
+ }
+
+ @Override
+ public int tcpPort() {
+ return tcpPort;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof DefaultControllerNode) {
+ DefaultControllerNode that = (DefaultControllerNode) o;
+ return Objects.equals(this.id, that.id);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("id", id)
+ .add("ip", ip).add("tcpPort", tcpPort).toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/Leadership.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/Leadership.java
new file mode 100644
index 00000000..113e19cb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/Leadership.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+import java.util.Objects;
+import java.util.List;
+import java.util.Optional;
+
+import org.joda.time.DateTime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Abstract leadership concept. The information carried by this construct
+ * include the topic of contention, the {@link NodeId}s of Nodes that could
+ * become leader for the topic, the epoch when the term for a given leader
+ * began, and the system time when the term began. Note:
+ * <ul>
+ * <li>The list of NodeIds may include the current leader at index 0, and the
+ * rest in decreasing preference order.</li>
+ * <li>The epoch is the logical age of a Leadership construct, and should be
+ * used for comparing two Leaderships, but only of the same topic.</li>
+ * <li>The leader may be null if its accuracy can't be guaranteed. This applies
+ * to CANDIDATES_CHANGED events and candidate board contents.</li>
+ * </ul>
+ */
+public class Leadership {
+
+ private final String topic;
+ private final Optional<NodeId> leader;
+ private final List<NodeId> candidates;
+ private final long epoch;
+ private final long electedTime;
+
+ public Leadership(String topic, NodeId leader, long epoch, long electedTime) {
+ this.topic = topic;
+ this.leader = Optional.of(leader);
+ this.candidates = ImmutableList.of(leader);
+ this.epoch = epoch;
+ this.electedTime = electedTime;
+ }
+
+ public Leadership(String topic, NodeId leader, List<NodeId> candidates,
+ long epoch, long electedTime) {
+ this.topic = topic;
+ this.leader = Optional.of(leader);
+ this.candidates = ImmutableList.copyOf(candidates);
+ this.epoch = epoch;
+ this.electedTime = electedTime;
+ }
+
+ public Leadership(String topic, List<NodeId> candidates,
+ long epoch, long electedTime) {
+ this.topic = topic;
+ this.leader = Optional.empty();
+ this.candidates = ImmutableList.copyOf(candidates);
+ this.epoch = epoch;
+ this.electedTime = electedTime;
+ }
+
+ /**
+ * The topic for which this leadership applies.
+ *
+ * @return leadership topic.
+ */
+ public String topic() {
+ return topic;
+ }
+
+ /**
+ * The nodeId of leader for this topic.
+ *
+ * @return leader node.
+ */
+ // This will return Optional<NodeId> in the future.
+ public NodeId leader() {
+ return leader.orElse(null);
+ }
+
+ /**
+ * Returns an preference-ordered list of nodes that are in the leadership
+ * race for this topic.
+ *
+ * @return a list of NodeIds in priority-order, or an empty list.
+ */
+ public List<NodeId> candidates() {
+ return candidates;
+ }
+
+ /**
+ * The epoch when the leadership was assumed.
+ * <p>
+ * Comparing epochs is only appropriate for leadership events for the same
+ * topic. The system guarantees that for any given topic the epoch for a new
+ * term is higher (not necessarily by 1) than the epoch for any previous
+ * term.
+ *
+ * @return leadership epoch
+ */
+ public long epoch() {
+ return epoch;
+ }
+
+ /**
+ * The system time when the term started.
+ * <p>
+ * The elected time is initially set on the node coordinating
+ * the leader election using its local system time. Due to possible
+ * clock skew, relying on this value for determining event ordering
+ * is discouraged. Epoch is more appropriate for determining
+ * event ordering.
+ *
+ * @return elected time.
+ */
+ public long electedTime() {
+ return electedTime;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(topic, leader, candidates, epoch, electedTime);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Leadership) {
+ final Leadership other = (Leadership) obj;
+ return Objects.equals(this.topic, other.topic) &&
+ Objects.equals(this.leader, other.leader) &&
+ Objects.equals(this.candidates, other.candidates) &&
+ Objects.equals(this.epoch, other.epoch) &&
+ Objects.equals(this.electedTime, other.electedTime);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this.getClass())
+ .add("topic", topic)
+ .add("leader", leader)
+ .add("candidates", candidates)
+ .add("epoch", epoch)
+ .add("electedTime", new DateTime(electedTime))
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java
new file mode 100644
index 00000000..faf6dd45
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.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.cluster;
+
+import java.util.Objects;
+
+import org.onosproject.event.AbstractEvent;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Describes leadership-related event.
+ */
+public class LeadershipEvent extends AbstractEvent<LeadershipEvent.Type, Leadership> {
+
+ /**
+ * Type of leadership-related events.
+ */
+ public enum Type {
+ /**
+ * Signifies that the leader has been elected. The event subject is the
+ * new leader.
+ */
+ LEADER_ELECTED,
+
+ /**
+ * Signifies that the leader has been re-elected. The event subject is the
+ * leader.
+ */
+ LEADER_REELECTED,
+
+ /**
+ * Signifies that the leader has been booted and lost leadership. The
+ * event subject is the former leader.
+ */
+ LEADER_BOOTED,
+
+ /**
+ * Signifies that the list of candidates for leadership for a topic has
+ * changed. This event does not guarantee accurate leader information.
+ */
+ CANDIDATES_CHANGED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified instance and the
+ * current time.
+ *
+ * @param type leadership event type
+ * @param leadership event subject
+ */
+ public LeadershipEvent(Type type, Leadership leadership) {
+ super(type, leadership);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified subject and time.
+ *
+ * @param type leadership event type
+ * @param leadership event subject
+ * @param time occurrence time
+ */
+ public LeadershipEvent(Type type, Leadership leadership, long time) {
+ super(type, leadership, time);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subject(), time());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof LeadershipEvent) {
+ final LeadershipEvent other = (LeadershipEvent) obj;
+ return Objects.equals(this.type(), other.type()) &&
+ Objects.equals(this.subject(), other.subject()) &&
+ Objects.equals(this.time(), other.time());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this.getClass())
+ .add("type", type())
+ .add("subject", subject())
+ .add("time", time())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEventListener.java
new file mode 100644
index 00000000..53d84b1b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipEventListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.cluster;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving device leadership-related events.
+ */
+public interface LeadershipEventListener extends EventListener<LeadershipEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipService.java
new file mode 100644
index 00000000..7d1f6079
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/LeadershipService.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import org.onosproject.event.ListenerService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Service for leader election.
+ * Leadership contests are organized around topics. A instance can join the
+ * leadership race for a topic or withdraw from a race it has previously joined.
+ * Listeners can be added to receive notifications asynchronously for various
+ * leadership contests.
+ */
+public interface LeadershipService
+ extends ListenerService<LeadershipEvent, LeadershipEventListener> {
+
+ /**
+ * Returns the current leader for the topic.
+ *
+ * @param path topic
+ * @return nodeId of the leader, null if so such topic exists.
+ */
+ NodeId getLeader(String path);
+
+ /**
+ * Returns the current leadership info for the topic.
+ *
+ * @param path topic
+ * @return leadership info or null if so such topic exists.
+ */
+ Leadership getLeadership(String path);
+
+ /**
+ * Returns the set of topics owned by the specified node.
+ *
+ * @param nodeId node Id.
+ * @return set of topics for which this node is the current leader.
+ */
+ Set<String> ownedTopics(NodeId nodeId);
+
+ /**
+ * Joins the leadership contest.
+ *
+ * @param path topic for which this controller node wishes to be a leader
+ * @return {@code Leadership} future
+ */
+ CompletableFuture<Leadership> runForLeadership(String path);
+
+ /**
+ * Withdraws from a leadership contest.
+ *
+ * @param path topic for which this controller node no longer wishes to be a leader
+ * @return future that is successfully completed when withdraw is done
+ */
+ CompletableFuture<Void> withdraw(String path);
+
+ /**
+ * If the local nodeId is the leader for specified topic, this method causes it to
+ * step down temporarily from leadership.
+ * <p>
+ * The node will continue to be in contention for leadership and can
+ * potentially become the leader again if and when it becomes the highest
+ * priority candidate
+ * <p>
+ * If the local nodeId is not the leader, this method will make no changes and
+ * simply return false.
+ *
+ * @param path topic for which this controller node should give up leadership
+ * @return true if this node stepped down from leadership, false otherwise
+ */
+ boolean stepdown(String path);
+
+ /**
+ * Moves the specified nodeId to the top of the candidates list for the topic.
+ * <p>
+ * If the node is not a candidate for this topic, this method will be a noop.
+ *
+ * @param path leadership topic
+ * @param nodeId nodeId to make the top candidate
+ * @return true if nodeId is now the top candidate, false otherwise
+ */
+ boolean makeTopCandidate(String path, NodeId nodeId);
+
+ /**
+ * Returns the current leader board.
+ *
+ * @return mapping from topic to leadership info.
+ */
+ Map<String, Leadership> getLeaderBoard();
+
+ /**
+ * Returns the candidates for all known topics.
+ *
+ * @return A mapping from topics to corresponding list of candidates.
+ */
+ Map<String, List<NodeId>> getCandidates();
+
+ /**
+ * Returns the candidates for a given topic.
+ *
+ * @param path topic
+ * @return A lists of NodeIds, which may be empty.
+ */
+ List<NodeId> getCandidates(String path);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/NodeId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/NodeId.java
new file mode 100644
index 00000000..68b490f2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/NodeId.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.cluster;
+
+import java.util.Objects;
+
+/**
+ * Controller cluster identity.
+ */
+public class NodeId {
+
+ private final String id;
+
+ /**
+ * Creates a new cluster node identifier from the specified string.
+ *
+ * @param id string identifier
+ */
+ public NodeId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof NodeId) {
+ final NodeId other = (NodeId) obj;
+ return Objects.equals(this.id, other.id);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java
new file mode 100644
index 00000000..081a6ba2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * An immutable container for role information for a device,
+ * within the current cluster. Role attributes include current
+ * master and a preference-ordered list of backup nodes.
+ */
+public class RoleInfo {
+ private final Optional<NodeId> master;
+ private final List<NodeId> backups;
+
+ public RoleInfo(NodeId master, List<NodeId> backups) {
+ this.master = Optional.ofNullable(master);
+ this.backups = ImmutableList.copyOf(backups);
+ }
+
+ public RoleInfo() {
+ this.master = Optional.empty();
+ this.backups = ImmutableList.of();
+ }
+
+ // This will return a Optional<NodeId> in the future.
+ public NodeId master() {
+ return master.orElseGet(() -> null);
+ }
+
+ public List<NodeId> backups() {
+ return backups;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof RoleInfo)) {
+ return false;
+ }
+ RoleInfo that = (RoleInfo) other;
+
+ return Objects.equals(this.master, that.master)
+ && Objects.equals(this.backups, that.backups);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(master, backups);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this.getClass())
+ .add("master", master.orElseGet(() -> null))
+ .add("backups", backups)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/package-info.java
new file mode 100644
index 00000000..d5ae40c2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/cluster/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.
+ */
+
+/**
+ * Set of abstractions for dealing with controller cluster related topics.
+ */
+package org.onosproject.cluster;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecContext.java
new file mode 100644
index 00000000..272c3e90
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.codec;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Context for codecs to use while encoding/decoding.
+ */
+public interface CodecContext {
+
+ /**
+ * Returns the JSON object mapper.
+ *
+ * @return object mapper
+ */
+ ObjectMapper mapper();
+
+ /**
+ * Returns the JSON codec for the specified entity class.
+ *
+ * @param entityClass entity class
+ * @param <T> entity type
+ * @return JSON codec; null if no codec available for the class
+ */
+ <T> JsonCodec<T> codec(Class<T> entityClass);
+
+ /**
+ * Returns reference to the specified service implementation.
+ *
+ * @param serviceClass service class
+ * @param <T> service type
+ * @return JSON codec; null if no codec available for the class
+ */
+ <T> T getService(Class<T> serviceClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecService.java
new file mode 100644
index 00000000..2705569d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/CodecService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.codec;
+
+import java.util.Set;
+
+/**
+ * Service for registering and retrieving JSON codecs for various entities.
+ */
+public interface CodecService {
+
+ /**
+ * Returns the set of classes with currently registered codecs.
+ *
+ * @return set of entity classes
+ */
+ Set<Class<?>> getCodecs();
+
+ /**
+ * Returns the JSON codec for the specified entity class.
+ *
+ * @param entityClass entity class
+ * @param <T> entity type
+ * @return JSON codec; null if no codec available for the class
+ */
+ <T> JsonCodec<T> getCodec(Class<T> entityClass);
+
+ /**
+ * Registers the specified JSON codec for the given entity class.
+ *
+ * @param entityClass entity class
+ * @param codec JSON codec
+ * @param <T> entity type
+ */
+ <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec);
+
+ /**
+ * Unregisters the JSON codec for the specified entity class.
+ *
+ * @param entityClass entity class
+ */
+ void unregisterCodec(Class<?> entityClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/codec/JsonCodec.java b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/JsonCodec.java
new file mode 100644
index 00000000..6df8f117
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/JsonCodec.java
@@ -0,0 +1,115 @@
+/*
+ * 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.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstraction of a codec capable for encoding/decoding arbitrary objects to/from JSON.
+ */
+public abstract class JsonCodec<T> {
+
+ /**
+ * Encodes the specified entity into JSON.
+ *
+ * @param entity entity to encode
+ * @param context encoding context
+ * @return JSON node
+ * @throws java.lang.UnsupportedOperationException if the codec does not
+ * support encode operations
+ */
+ public ObjectNode encode(T entity, CodecContext context) {
+ throw new UnsupportedOperationException("encode() not supported");
+ }
+
+ /**
+ * Decodes the specified entity from JSON.
+ *
+ * @param json JSON to decode
+ * @param context decoding context
+ * @return decoded entity
+ * @throws java.lang.UnsupportedOperationException if the codec does not
+ * support decode operations
+ */
+ public T decode(ObjectNode json, CodecContext context) {
+ throw new UnsupportedOperationException("decode() not supported");
+ }
+
+ /**
+ * Encodes the collection of the specified entities.
+ *
+ * @param entities collection of entities to encode
+ * @param context encoding context
+ * @return JSON array
+ * @throws java.lang.UnsupportedOperationException if the codec does not
+ * support encode operations
+ */
+ public ArrayNode encode(Iterable<T> entities, CodecContext context) {
+ ArrayNode result = context.mapper().createArrayNode();
+ for (T entity : entities) {
+ result.add(encode(entity, context));
+ }
+ return result;
+ }
+
+ /**
+ * Decodes the specified JSON array into a collection of entities.
+ *
+ * @param json JSON array to decode
+ * @param context decoding context
+ * @return collection of decoded entities
+ * @throws java.lang.UnsupportedOperationException if the codec does not
+ * support decode operations
+ */
+ public List<T> decode(ArrayNode json, CodecContext context) {
+ List<T> result = new ArrayList<>();
+ for (JsonNode node : json) {
+ result.add(decode((ObjectNode) node, context));
+ }
+ return result;
+ }
+
+ /**
+ * Gets a child Object Node from a parent by name. If the child is not found
+ * or does nor represent an object, null is returned.
+ *
+ * @param parent parent object
+ * @param childName name of child to query
+ * @return child object if found, null if not found or if not an object
+ */
+ protected static ObjectNode get(ObjectNode parent, String childName) {
+ JsonNode node = parent.path(childName);
+ return node.isObject() && !node.isNull() ? (ObjectNode) node : null;
+ }
+
+ /**
+ * Gets a child Object Node from a parent by index. If the child is not found
+ * or does nor represent an object, null is returned.
+ *
+ * @param parent parent object
+ * @param childIndex index of child to query
+ * @return child object if found, null if not found or if not an object
+ */
+ protected static ObjectNode get(JsonNode parent, int childIndex) {
+ JsonNode node = parent.path(childIndex);
+ return node.isObject() && !node.isNull() ? (ObjectNode) node : null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/codec/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/package-info.java
new file mode 100644
index 00000000..0aa063f5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/codec/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.
+ */
+
+/**
+ * Base JSON codec abstraction and a service for tracking various JSON codecs.
+ */
+package org.onosproject.codec;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java
new file mode 100644
index 00000000..fca53843
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java
@@ -0,0 +1,87 @@
+/*
+ * 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.core;
+
+import org.onosproject.security.Permission;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Abstraction of a network control/management application.
+ */
+public interface Application {
+
+ /**
+ * Returns the application name id.
+ *
+ * @return application identifier
+ */
+ ApplicationId id();
+
+ /**
+ * Returns the application version.
+ *
+ * @return application version
+ */
+ Version version();
+
+ /**
+ * Returns description of the application.
+ *
+ * @return application description text
+ */
+ String description();
+
+ /**
+ * Returns the name of the application origin, group or company.
+ *
+ * @return application origin
+ */
+ String origin();
+
+ /**
+ * Returns the role of the application.
+ *
+ * @return application role
+ */
+ ApplicationRole role();
+
+ /**
+ * Returns the permissions requested by the application.
+ *
+ * @return requested permissions
+ */
+ Set<Permission> permissions();
+
+ /**
+ * Returns the feature repository URI. Null value signifies that the
+ * application did not provide its own features repository.
+ *
+ * @return optional feature repo URL
+ */
+ Optional<URI> featuresRepo();
+
+ /**
+ * Returns the list of features comprising the application. At least one
+ * feature must be given.
+ *
+ * @return application features
+ */
+ List<String> features();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationId.java
new file mode 100644
index 00000000..25bc8ce7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationId.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+
+/**
+ * Application identifier.
+ */
+public interface ApplicationId {
+
+ /**
+ * Returns the application id.
+ * @return a short value
+ */
+ short id();
+
+ /**
+ * Returns the applications supplied identifier.
+ * @return a string identifier
+ */
+ String name();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java
new file mode 100644
index 00000000..f857fbdc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+// FIXME: Move to org.onosproject.app package
+
+import java.util.Set;
+
+/**
+ * Manages application IDs.
+ */
+public interface ApplicationIdStore {
+
+ /**
+ * Returns the set of currently registered application identifiers.
+ *
+ * @return set of application ids
+ */
+ Set<ApplicationId> getAppIds();
+
+ /**
+ * Returns an existing application id from a given id.
+ *
+ * @param id the short value of the id
+ * @return an application id; null if no such app registered
+ */
+ ApplicationId getAppId(Short id);
+
+ /**
+ * Returns registered application id from the given name.
+ *
+ * @param name application name
+ * @return an application id; null if no such app registered
+ */
+ ApplicationId getAppId(String name);
+
+ /**
+ * Registers a new application by its name, which is expected
+ * to follow the reverse DNS convention, e.g.
+ * {@code org.flying.circus.app}
+ *
+ * @param identifier string identifier
+ * @return the application id
+ */
+ ApplicationId registerApplication(String identifier);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationRole.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationRole.java
new file mode 100644
index 00000000..5fcb80ba
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/ApplicationRole.java
@@ -0,0 +1,38 @@
+/*
+ * 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.core;
+
+public enum ApplicationRole {
+ /**
+ * Indicates that an application has an ADMIN role.
+ */
+ ADMIN,
+
+ /**
+ * Indicates that an application has a USER role.
+ */
+ USER,
+
+ /**
+ * Indicates that an application role has not been specified.
+ */
+ UNSPECIFIED,
+
+ /**
+ * More useful roles may be defined.
+ */
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/CoreService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/CoreService.java
new file mode 100644
index 00000000..3dfc6b26
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/CoreService.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Set;
+
+/**
+ * Service for interacting with the core system of the controller.
+ */
+public interface CoreService {
+
+ /**
+ * Name of the core "application".
+ */
+ static final String CORE_APP_NAME = "org.onosproject.core";
+
+ /**
+ * Identifier of the core "provider".
+ */
+ static final ProviderId CORE_PROVIDER_ID = new ProviderId("core", CORE_APP_NAME);
+
+ /**
+ * Returns the product version.
+ *
+ * @return product version
+ */
+ Version version();
+
+ /**
+ * Returns the set of currently registered application identifiers.
+ *
+ * @return set of application ids
+ */
+ Set<ApplicationId> getAppIds();
+
+ /**
+ * Returns an existing application id from a given id.
+ * @param id the short value of the id
+ * @return an application id
+ */
+ ApplicationId getAppId(Short id);
+
+ /**
+ * Returns an existing application id from a given id.
+ * @param name the name portion of the ID to look up
+ * @return an application id
+ */
+ ApplicationId getAppId(String name);
+
+ /**
+ * Registers a new application by its name, which is expected
+ * to follow the reverse DNS convention, e.g.
+ * {@code org.flying.circus.app}
+ *
+ * @param identifier string identifier
+ * @return the application id
+ */
+ ApplicationId registerApplication(String identifier);
+
+ /**
+ * Returns an id generator for a given topic.
+ *
+ * @param topic topic identified
+ * @return the id generator
+ */
+ IdGenerator getIdGenerator(String topic);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
new file mode 100644
index 00000000..d8062ddf
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.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.core;
+
+import org.onosproject.security.Permission;
+
+import java.net.URI;
+import java.util.Set;
+import java.util.Optional;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of network control/management application descriptor.
+ */
+public class DefaultApplication implements Application {
+
+ private final ApplicationId appId;
+ private final Version version;
+ private final String description;
+ private final String origin;
+ private final ApplicationRole role;
+ private final Set<Permission> permissions;
+ private final Optional<URI> featuresRepo;
+ private final List<String> features;
+
+ /**
+ * Creates a new application descriptor using the supplied data.
+ *
+ * @param appId application identifier
+ * @param version application version
+ * @param description application description
+ * @param origin origin company
+ * @param role application role
+ * @param permissions requested permissions
+ * @param featuresRepo optional features repo URI
+ * @param features application features
+ */
+ public DefaultApplication(ApplicationId appId, Version version,
+ String description, String origin,
+ ApplicationRole role, Set<Permission> permissions,
+ Optional<URI> featuresRepo, List<String> features) {
+ this.appId = checkNotNull(appId, "ID cannot be null");
+ this.version = checkNotNull(version, "Version cannot be null");
+ this.description = checkNotNull(description, "Description cannot be null");
+ this.origin = checkNotNull(origin, "Origin cannot be null");
+ this.role = checkNotNull(role, "Role cannot be null");
+ this.permissions = checkNotNull(permissions, "Permissions cannot be null");
+ this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null");
+ this.features = checkNotNull(features, "Features cannot be null");
+ checkArgument(!features.isEmpty(), "There must be at least one feature");
+ }
+
+ @Override
+ public ApplicationId id() {
+ return appId;
+ }
+
+ @Override
+ public Version version() {
+ return version;
+ }
+
+ @Override
+ public String description() {
+ return description;
+ }
+
+ @Override
+ public String origin() {
+ return origin;
+ }
+
+ @Override
+ public ApplicationRole role() {
+ return role;
+ }
+
+ @Override
+ public Set<Permission> permissions() {
+ return permissions;
+ }
+
+ @Override
+ public Optional<URI> featuresRepo() {
+ return featuresRepo;
+ }
+
+ @Override
+ public List<String> features() {
+ return features;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(appId, version, description, origin, role, permissions,
+ featuresRepo, features);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final DefaultApplication other = (DefaultApplication) obj;
+ return Objects.equals(this.appId, other.appId) &&
+ Objects.equals(this.version, other.version) &&
+ Objects.equals(this.description, other.description) &&
+ Objects.equals(this.origin, other.origin) &&
+ Objects.equals(this.role, other.role) &&
+ Objects.equals(this.permissions, other.permissions) &&
+ Objects.equals(this.featuresRepo, other.featuresRepo) &&
+ Objects.equals(this.features, other.features);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("appId", appId)
+ .add("version", version)
+ .add("description", description)
+ .add("origin", origin)
+ .add("role", role)
+ .add("permissions", permissions)
+ .add("featuresRepo", featuresRepo)
+ .add("features", features)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java
new file mode 100644
index 00000000..c7b5b2da
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Application identifier.
+ */
+public class DefaultApplicationId implements ApplicationId {
+
+ private final short id;
+ private final String name;
+
+ /**
+ * Creates a new application ID.
+ *
+ * @param id application identifier
+ * @param name application name
+ */
+ public DefaultApplicationId(int id, String name) {
+ checkArgument(0 <= id && id <= Short.MAX_VALUE, "id is outside range");
+ this.id = (short) id;
+ this.name = name;
+ }
+
+ // Constructor for serializers.
+ private DefaultApplicationId() {
+ this.id = 0;
+ this.name = null;
+ }
+
+ @Override
+ public short id() {
+ return id;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultApplicationId) {
+ DefaultApplicationId other = (DefaultApplicationId) obj;
+ return Objects.equals(this.id, other.id);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("id", id).add("name", name).toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java
new file mode 100644
index 00000000..9fa8d2b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultGroupId.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Default implementation of {@link GroupId}.
+ */
+public class DefaultGroupId implements GroupId {
+
+ private final int id;
+
+ public DefaultGroupId(int id) {
+ this.id = id;
+ }
+
+ // Constructor for serialization
+ private DefaultGroupId() {
+ this.id = 0;
+ }
+
+ @Override
+ public int id() {
+ return this.id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DefaultGroupId)) {
+ return false;
+ }
+ final DefaultGroupId other = (DefaultGroupId) obj;
+ return Objects.equals(this.id, other.id);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/GroupId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/GroupId.java
new file mode 100644
index 00000000..739fc7ff
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/GroupId.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Group identifier.
+ */
+public interface GroupId {
+
+ /**
+ * Returns a group ID as an integer value.
+ * The method is not intended for use by application developers.
+ * Return data type may change in the future release.
+ *
+ * @return a group ID as integer value
+ */
+ int id();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlock.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlock.java
new file mode 100644
index 00000000..f4088603
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlock.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A class representing an ID space.
+ */
+public final class IdBlock {
+ private final long start;
+ private final long size;
+
+ private final AtomicLong currentId;
+
+ /**
+ * Constructs a new ID block with the specified size and initial value.
+ *
+ * @param start initial value of the block
+ * @param size size of the block
+ * @throws IllegalArgumentException if the size is less than or equal to 0
+ */
+ public IdBlock(long start, long size) {
+ checkArgument(size > 0, "size should be more than 0, but %s", size);
+
+ this.start = start;
+ this.size = size;
+
+ this.currentId = new AtomicLong(start);
+ }
+
+ /**
+ * Returns the initial value.
+ *
+ * @return initial value
+ */
+ private long getStart() {
+ return start;
+ }
+
+ /**
+ * Returns the last value.
+ *
+ * @return last value
+ */
+ private long getEnd() {
+ return start + size - 1;
+ }
+
+ /**
+ * Returns the block size.
+ *
+ * @return block size
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the next ID in the block.
+ *
+ * @return next ID
+ * @throws UnavailableIdException if there is no available ID in the block.
+ */
+ public long getNextId() {
+ final long id = currentId.getAndIncrement();
+ if (id > getEnd()) {
+ throw new UnavailableIdException(String.format(
+ "used all IDs in allocated space (size: %d, end: %d, current: %d)",
+ size, getEnd(), id
+ ));
+ }
+
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("start", start)
+ .add("size", size)
+ .add("currentId", currentId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlockStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlockStore.java
new file mode 100644
index 00000000..8ed58ae7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdBlockStore.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Manages id blocks.
+ */
+public interface IdBlockStore {
+
+ /**
+ * Returns a topic-unique block of ids.
+ *
+ * @param topic topic name
+ * @return id block
+ */
+ IdBlock getIdBlock(String topic);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdGenerator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdGenerator.java
new file mode 100644
index 00000000..149c834b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/IdGenerator.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * A generalized interface for ID generation
+ *
+ * {@link #getNewId()} generates a globally unique ID instance on
+ * each invocation.
+ */
+public interface IdGenerator {
+ /**
+ * Returns a globally unique ID instance.
+ *
+ * @return globally unique ID instance
+ */
+ long getNewId();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/MetricsHelper.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/MetricsHelper.java
new file mode 100644
index 00000000..b5be0944
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/MetricsHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import org.onlab.metrics.MetricsComponent;
+import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.MetricsService;
+
+import com.codahale.metrics.Timer;
+
+/**
+ * Collection of utility methods used for providing Metrics.
+ */
+public interface MetricsHelper {
+
+ /**
+ * Returns MetricService instance.
+ *
+ * @return MetricService instance
+ */
+ abstract MetricsService metricsService();
+
+
+ /**
+ * Creates a Timer instance with given name.
+ *
+ * @param component component name
+ * @param feature feature name
+ * @param name timer name
+ * @return Timer instance
+ */
+ default Timer createTimer(String component, String feature, String name) {
+ final MetricsService metricsService = metricsService();
+ if (metricsService != null) {
+ MetricsComponent c = metricsService.registerComponent(component);
+ MetricsFeature f = c.registerFeature(feature);
+ return metricsService.createTimer(c, f, name);
+ }
+ return null;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/UnavailableIdException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/UnavailableIdException.java
new file mode 100644
index 00000000..2e4af264
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/UnavailableIdException.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * Represents that there is no available IDs.
+ */
+public class UnavailableIdException extends RuntimeException {
+
+ private static final long serialVersionUID = -2287403908433720122L;
+
+ /**
+ * Constructs an exception with no message and no underlying cause.
+ */
+ public UnavailableIdException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message describing the specific nature of the error
+ */
+ public UnavailableIdException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and the underlying cause.
+ *
+ * @param message the message describing the specific nature of the error
+ * @param cause the underlying cause of this error
+ */
+ public UnavailableIdException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/Version.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/Version.java
new file mode 100644
index 00000000..a5377016
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/Version.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.lang.Integer.parseInt;
+
+/**
+ * Representation of the product version.
+ */
+public final class Version {
+
+ public static final String FORMAT_MINIMAL = "%d.%d";
+ public static final String FORMAT_SHORT = "%d.%d.%s";
+ public static final String FORMAT_LONG = "%d.%d.%s.%s";
+
+ private static final String NEGATIVE = "Version segment cannot be negative";
+ public static final String TOO_SHORT = "Version must have at least major and minor numbers";
+
+ private final int major;
+ private final int minor;
+ private final String patch;
+ private final String build;
+
+ private final String format;
+
+ // Creates a new version descriptor
+ private Version(int major, int minor, String patch, String build) {
+ this.major = major;
+ this.minor = minor;
+ this.patch = patch;
+ this.build = build;
+ this.format =
+ isNullOrEmpty(patch) ?
+ String.format(FORMAT_MINIMAL, major, minor) :
+ (isNullOrEmpty(build) ?
+ String.format(FORMAT_SHORT, major, minor, patch) :
+ String.format(FORMAT_LONG, major, minor, patch, build));
+ }
+
+
+ /**
+ * Creates a new version from the specified constituent numbers.
+ *
+ * @param major major version number
+ * @param minor minor version number
+ * @param patch version patch segment
+ * @param build optional build string
+ * @return version descriptor
+ */
+ public static Version version(int major, int minor, String patch, String build) {
+ checkArgument(major >= 0, NEGATIVE);
+ checkArgument(minor >= 0, NEGATIVE);
+ return new Version(major, minor, patch, build);
+ }
+
+ /**
+ * Creates a new version by parsing the specified string.
+ *
+ * @param string version string
+ * @return version descriptor
+ */
+ public static Version version(String string) {
+ String[] fields = string.split("[.-]");
+ checkArgument(fields.length >= 2, TOO_SHORT);
+ return new Version(parseInt(fields[0]), parseInt(fields[1]),
+ fields.length >= 3 ? fields[2] : null,
+ fields.length >= 4 ? fields[3] : null);
+ }
+
+ /**
+ * Returns the major version number.
+ *
+ * @return major version number
+ */
+ public int major() {
+ return major;
+ }
+
+ /**
+ * Returns the minor version number.
+ *
+ * @return minor version number
+ */
+ public int minor() {
+ return minor;
+ }
+
+ /**
+ * Returns the version patch segment.
+ *
+ * @return patch number
+ */
+ public String patch() {
+ return patch;
+ }
+
+ /**
+ * Returns the version build string.
+ *
+ * @return build string
+ */
+ public String build() {
+ return build;
+ }
+
+ @Override
+ public String toString() {
+ return format;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(format);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Version) {
+ final Version other = (Version) obj;
+ return Objects.equals(this.format, other.format);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/package-info.java
new file mode 100644
index 00000000..3766d49f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/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.
+ */
+
+/**
+ * ONOS Core API definitions.
+ */
+package org.onosproject.core;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractEvent.java
new file mode 100644
index 00000000..67b10292
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractEvent.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import org.joda.time.LocalDateTime;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Base event implementation.
+ */
+public class AbstractEvent<T extends Enum, S> implements Event<T, S> {
+
+ private final long time;
+ private final T type;
+ private final S subject;
+
+ /**
+ * Creates an event of a given type and for the specified subject and the
+ * current time.
+ *
+ * @param type event type
+ * @param subject event subject
+ */
+ protected AbstractEvent(T type, S subject) {
+ this(type, subject, System.currentTimeMillis());
+ }
+
+ /**
+ * Creates an event of a given type and for the specified subject and time.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param time occurrence time
+ */
+ protected AbstractEvent(T type, S subject, long time) {
+ this.type = type;
+ this.subject = subject;
+ this.time = time;
+ }
+
+ @Override
+ public long time() {
+ return time;
+ }
+
+ @Override
+ public T type() {
+ return type;
+ }
+
+ @Override
+ public S subject() {
+ return subject;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("time", new LocalDateTime(time))
+ .add("type", type())
+ .add("subject", subject())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractListenerManager.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractListenerManager.java
new file mode 100644
index 00000000..cbe7421f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/AbstractListenerManager.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.event;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+
+/**
+ * Basis for components which need to export listener mechanism.
+ */
+@Component(componentAbstract = true)
+public abstract class AbstractListenerManager<E extends Event, L extends EventListener<E>>
+ implements ListenerService<E, L> {
+
+ protected final ListenerRegistry<E, L> listenerRegistry = new ListenerRegistry<>();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ @Override
+ public void addListener(L listener) {
+ listenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(L listener) {
+ listenerRegistry.removeListener(listener);
+ }
+
+
+ /**
+ * Safely posts the specified event to the local event dispatcher.
+ * If there is no event dispatcher or if the event is null, this method
+ * is a noop.
+ *
+ * @param event event to be posted; may be null
+ */
+ protected void post(E event) {
+ if (event != null && eventDispatcher != null) {
+ eventDispatcher.post(event);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/DefaultEventSinkRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/DefaultEventSinkRegistry.java
new file mode 100644
index 00000000..be6ddb61
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/DefaultEventSinkRegistry.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base implementation of event sink registry.
+ */
+public class DefaultEventSinkRegistry implements EventSinkRegistry {
+
+ private final Map<Class<? extends Event>, EventSink<? extends Event>>
+ sinks = new ConcurrentHashMap<>();
+
+ @Override
+ public <E extends Event> void addSink(Class<E> eventClass, EventSink<E> sink) {
+ checkNotNull(eventClass, "Event class cannot be null");
+ checkNotNull(sink, "Event sink cannot be null");
+ checkArgument(!sinks.containsKey(eventClass),
+ "Event sink already registered for %s", eventClass.getName());
+ sinks.put(eventClass, sink);
+ }
+
+ @Override
+ public <E extends Event> void removeSink(Class<E> eventClass) {
+ checkNotNull(eventClass, "Event class cannot be null");
+ checkArgument(sinks.remove(eventClass) != null,
+ "Event sink not registered for %s", eventClass.getName());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <E extends Event> EventSink<E> getSink(Class<E> eventClass) {
+ checkNotNull(eventClass, "Event class cannot be null");
+ return (EventSink<E>) sinks.get(eventClass);
+ }
+
+ @Override
+ public Set<Class<? extends Event>> getSinks() {
+ return ImmutableSet.copyOf(sinks.keySet());
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/Event.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/Event.java
new file mode 100644
index 00000000..e7cbb60f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/Event.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of an of a time-stamped event pertaining to an arbitrary subject.
+ */
+public interface Event<T extends Enum, S> {
+
+ /**
+ * Returns the timestamp of when the event occurred, given in milliseconds
+ * since the start of epoch.
+ *
+ * @return timestamp in milliseconds
+ */
+ long time();
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return event type
+ */
+ T type();
+
+ /**
+ * Returns the subject of the event.
+ *
+ * @return subject to which this event pertains
+ */
+ S subject();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDeliveryService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDeliveryService.java
new file mode 100644
index 00000000..ff268935
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDeliveryService.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.event;
+
+/**
+ * Abstraction of an entity capable of accepting events to be posted and
+ * then dispatching them to the appropriate event sink.
+ */
+public interface EventDeliveryService extends EventDispatcher, EventSinkRegistry {
+
+ /**
+ * Sets the number of millis that an event sink has to process an event.
+ *
+ * @param millis number of millis allowed per sink per event
+ */
+ void setDispatchTimeLimit(long millis);
+
+ /**
+ * Returns the number of millis that an event sink has to process an event.
+ *
+ * @return number of millis allowed per sink per event
+ */
+ long getDispatchTimeLimit();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDispatcher.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDispatcher.java
new file mode 100644
index 00000000..daebd8b0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventDispatcher.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of a mechanism capable of accepting and dispatching events to
+ * appropriate event sinks. Where the event sinks are obtained is unspecified.
+ * Similarly, whether the events are accepted and dispatched synchronously
+ * or asynchronously is unspecified as well.
+ */
+public interface EventDispatcher {
+
+ /**
+ * Posts the specified event for dispatching.
+ *
+ * @param event event to be posted
+ */
+ void post(Event event);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventFilter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventFilter.java
new file mode 100644
index 00000000..3b2498f6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventFilter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.event;
+
+/**
+ * Entity capable of filtering events.
+ */
+public interface EventFilter<E extends Event> {
+
+ /**
+ * Indicates whether the specified event is of interest or not.
+ * Default implementation always returns true.
+ *
+ * @param event event to be inspected
+ * @return true if event is relevant; false otherwise
+ */
+ default boolean isRelevant(E event) {
+ return true;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventListener.java
new file mode 100644
index 00000000..e77369fc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Entity capable of receiving events.
+ */
+public interface EventListener<E extends Event> extends EventFilter<E> {
+
+ /**
+ * Reacts to the specified event.
+ *
+ * @param event event to be processed
+ */
+ void event(E event);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSink.java
new file mode 100644
index 00000000..221b3224
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSink.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of an event sink capable of processing the specified event types.
+ */
+public interface EventSink<E extends Event> {
+
+ /**
+ * Processes the specified event.
+ *
+ * @param event event to be processed
+ */
+ void process(E event);
+
+ /**
+ * Handles notification that event processing time limit has been exceeded.
+ */
+ default void onProcessLimit() {
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSinkRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSinkRegistry.java
new file mode 100644
index 00000000..bb054a30
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/EventSinkRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+/**
+ * Abstraction of an event sink registry capable of tracking sinks based on
+ * their event class.
+ */
+public interface EventSinkRegistry {
+
+ /**
+ * Adds the specified sink for the given event class.
+ *
+ * @param eventClass event class
+ * @param sink event sink
+ * @param <E> type of event
+ */
+ <E extends Event> void addSink(Class<E> eventClass, EventSink<E> sink);
+
+ /**
+ * Removes the sink associated with the given event class.
+ *
+ * @param eventClass event class
+ * @param <E> type of event
+ */
+ <E extends Event> void removeSink(Class<E> eventClass);
+
+ /**
+ * Returns the event sink associated with the specified event class.
+ *
+ * @param eventClass event class
+ * @param <E> type of event
+ * @return event sink or null if none found
+ */
+ <E extends Event> EventSink<E> getSink(Class<E> eventClass);
+
+ /**
+ * Returns the set of all event classes for which sinks are presently
+ * registered.
+ *
+ * @return set of event classes
+ */
+ Set<Class<? extends Event>> getSinks();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerRegistry.java
new file mode 100644
index 00000000..ef02af06
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerRegistry.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Base implementation of an event sink and a registry capable of tracking
+ * listeners and dispatching events to them as part of event sink processing.
+ */
+public class ListenerRegistry<E extends Event, L extends EventListener<E>>
+ implements ListenerService<E, L>, EventSink<E> {
+
+ private static final long LIMIT = 1_800; // ms
+
+ private final Logger log = getLogger(getClass());
+
+ private long lastStart;
+ private L lastListener;
+
+ /**
+ * Set of listeners that have registered.
+ */
+ protected final Set<L> listeners = new CopyOnWriteArraySet<>();
+
+ @Override
+ public void addListener(L listener) {
+ checkNotNull(listener, "Listener cannot be null");
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(L listener) {
+ checkNotNull(listener, "Listener cannot be null");
+ if (!listeners.remove(listener)) {
+ log.warn("Listener {} not registered", listener);
+ }
+ }
+
+ @Override
+ public void process(E event) {
+ for (L listener : listeners) {
+ try {
+ lastListener = listener;
+ lastStart = System.currentTimeMillis();
+ if (listener.isRelevant(event)) {
+ listener.event(event);
+ }
+ lastStart = 0;
+ } catch (Exception error) {
+ reportProblem(event, error);
+ }
+ }
+ }
+
+ @Override
+ public void onProcessLimit() {
+ if (lastStart > 0) {
+ long duration = System.currentTimeMillis() - lastStart;
+ if (duration > LIMIT) {
+ log.error("Listener {} exceeded execution time limit: {} ms; ejected",
+ lastListener.getClass().getName(),
+ duration);
+ removeListener(lastListener);
+ }
+ lastStart = 0;
+ }
+ }
+
+ /**
+ * Reports a problem encountered while processing an event.
+ *
+ * @param event event being processed
+ * @param error error encountered while processing
+ */
+ protected void reportProblem(E event, Throwable error) {
+ log.warn("Exception encountered while processing event " + event, error);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerService.java
new file mode 100644
index 00000000..a4a36319
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/ListenerService.java
@@ -0,0 +1,38 @@
+/*
+ * 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.event;
+
+/**
+ * Abstraction of a service capable of asynchronously notifying listeners.
+ */
+public interface ListenerService<E extends Event, L extends EventListener<E>> {
+
+ /**
+ * Adds the specified listener.
+ *
+ * @param listener listener to be added
+ */
+ void addListener(L listener);
+
+ /**
+ * Removes the specified listener.
+ *
+ * @param listener listener to be removed
+ */
+ void removeListener(L listener);
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/event/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/event/package-info.java
new file mode 100644
index 00000000..6b10bcf5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/event/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 delivery subsystem interfaces &amp; supporting abstractions.
+ */
+package org.onosproject.event;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java
new file mode 100644
index 00000000..a8835fc7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipAdminService.java
@@ -0,0 +1,45 @@
+/*
+ * 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.mastership;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+
+/**
+ * Service for administering the inventory of device masterships.
+ */
+public interface MastershipAdminService {
+
+ /**
+ * Applies the current mastership role for the specified device.
+ *
+ * @param instance controller instance identifier
+ * @param deviceId device identifier
+ * @param role requested role
+ * @return future that is completed when the role is set
+ */
+ CompletableFuture<Void> setRole(NodeId instance, DeviceId deviceId, MastershipRole role);
+
+ /**
+ * Balances the mastership to be shared as evenly as possibly by all
+ * online instances.
+ */
+ void balanceRoles();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipEvent.java
new file mode 100644
index 00000000..35c32e79
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipEvent.java
@@ -0,0 +1,95 @@
+/*
+ * 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.mastership;
+
+import org.joda.time.LocalDateTime;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.DeviceId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Describes a device mastership event.
+ */
+public class MastershipEvent extends AbstractEvent<MastershipEvent.Type, DeviceId> {
+
+ //Contains master and standby information.
+ RoleInfo roleInfo;
+
+ /**
+ * Type of mastership events.
+ */
+ public enum Type {
+ /**
+ * Signifies that the master for a device has changed.
+ */
+ MASTER_CHANGED,
+
+ /**
+ * Signifies that the list of backup nodes has changed. If
+ * the change in the backups list is accompanied by a change in
+ * master, the event is subsumed by MASTER_CHANGED.
+ */
+ BACKUPS_CHANGED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified device,
+ * role information, and the current time.
+ *
+ * @param type mastership event type
+ * @param device event device subject
+ * @param info mastership role information
+ */
+ public MastershipEvent(Type type, DeviceId device, RoleInfo info) {
+ super(type, device);
+ this.roleInfo = info;
+ }
+
+ /**
+ * Creates an event of a given type and for the specified device, master,
+ * and time.
+ *
+ * @param type mastership event type
+ * @param device event device subject
+ * @param info role information
+ * @param time occurrence time
+ */
+ public MastershipEvent(Type type, DeviceId device, RoleInfo info, long time) {
+ super(type, device, time);
+ this.roleInfo = info;
+ }
+
+ /**
+ * Returns the current role state for the subject.
+ *
+ * @return RoleInfo associated with Device ID subject
+ */
+ public RoleInfo roleInfo() {
+ return roleInfo;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("time", new LocalDateTime(time()))
+ .add("type", type())
+ .add("subject", subject())
+ .add("roleInfo", roleInfo)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipListener.java
new file mode 100644
index 00000000..9c5690e2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.mastership;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving device mastership-related events.
+ */
+public interface MastershipListener extends EventListener<MastershipEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipService.java
new file mode 100644
index 00000000..a709f5cf
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipService.java
@@ -0,0 +1,101 @@
+/*
+ * 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.mastership;
+
+import static org.onosproject.net.MastershipRole.MASTER;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+
+/**
+ * Service responsible for determining the controller instance mastership of
+ * a device in a clustered environment. This is the central authority for
+ * determining mastership, but is not responsible for actually applying it
+ * to the devices; this falls on the device service.
+ */
+public interface MastershipService
+ extends ListenerService<MastershipEvent, MastershipListener> {
+
+ /**
+ * Returns the role of the local node for the specified device, without
+ * triggering master selection.
+ *
+ * @param deviceId the the identifier of the device
+ * @return role of the current node
+ */
+ MastershipRole getLocalRole(DeviceId deviceId);
+
+ /**
+ * Returns true if the local controller is the Master for the specified deviceId.
+ *
+ * @param deviceId the the identifier of the device
+ * @return true if local node is master; false otherwise
+ */
+ default boolean isLocalMaster(DeviceId deviceId) {
+ return getLocalRole(deviceId) == MASTER;
+ }
+
+ /**
+ * Returns the mastership status of the local controller for a given
+ * device forcing master selection if necessary.
+ *
+ * @param deviceId the the identifier of the device
+ * @return the role of this controller instance
+ */
+ CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId);
+
+ /**
+ * Abandons mastership of the specified device on the local node thus
+ * forcing selection of a new master. If the local node is not a master
+ * for this device, no master selection will occur.
+ *
+ * @param deviceId the identifier of the device
+ * @return future that is completed when relinquish is complete
+ */
+ CompletableFuture<Void> relinquishMastership(DeviceId deviceId);
+
+ /**
+ * Returns the current master for a given device.
+ *
+ * @param deviceId the identifier of the device
+ * @return the ID of the master controller for the device
+ */
+ NodeId getMasterFor(DeviceId deviceId);
+
+ /**
+ * Returns controllers connected to a given device, in order of
+ * preference. The first entry in the list is the current master.
+ *
+ * @param deviceId the identifier of the device
+ * @return a list of controller IDs
+ */
+ RoleInfo getNodesFor(DeviceId deviceId);
+
+ /**
+ * Returns the devices for which a controller is master.
+ *
+ * @param nodeId the ID of the controller
+ * @return a set of device IDs
+ */
+ Set<DeviceId> getDevicesOf(NodeId nodeId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java
new file mode 100644
index 00000000..81c2d8b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStore.java
@@ -0,0 +1,125 @@
+/*
+ * 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.mastership;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.store.Store;
+
+/**
+ * Manages inventory of mastership roles for devices, across controller
+ * instances; not intended for direct use.
+ */
+public interface MastershipStore extends Store<MastershipEvent, MastershipStoreDelegate> {
+
+ // three things to map: NodeId, DeviceId, MastershipRole
+
+ /**
+ * Requests role of the local node for the specified device.
+ *
+ * @param deviceId device identifier
+ * @return established or newly negotiated mastership role
+ */
+ CompletableFuture<MastershipRole> requestRole(DeviceId deviceId);
+
+ /**
+ * Returns the role of a device for a specific controller instance.
+ *
+ * @param nodeId the instance identifier
+ * @param deviceId the device identifiers
+ * @return the role
+ */
+ MastershipRole getRole(NodeId nodeId, DeviceId deviceId);
+
+ /**
+ * Returns the master for a device.
+ *
+ * @param deviceId the device identifier
+ * @return the instance identifier of the master
+ */
+ NodeId getMaster(DeviceId deviceId);
+
+ /**
+ * Returns the master and backup nodes for a device.
+ *
+ * @param deviceId the device identifier
+ * @return a RoleInfo containing controller IDs
+ */
+ RoleInfo getNodes(DeviceId deviceId);
+
+ /**
+ * Returns the devices that a controller instance is master of.
+ *
+ * @param nodeId the instance identifier
+ * @return a set of device identifiers
+ */
+ Set<DeviceId> getDevices(NodeId nodeId);
+
+
+ /**
+ * Sets a device's role for a specified controller instance.
+ *
+ * @param nodeId controller instance identifier
+ * @param deviceId device identifier
+ * @return a mastership event
+ */
+ CompletableFuture<MastershipEvent> setMaster(NodeId nodeId, DeviceId deviceId);
+
+ /**
+ * Returns the current master and number of past mastership hand-offs
+ * (terms) for a device.
+ *
+ * @param deviceId the device identifier
+ * @return the current master's ID and the term value for device, or null
+ */
+ MastershipTerm getTermFor(DeviceId deviceId);
+
+ /**
+ * Sets a controller instance's mastership role to STANDBY for a device.
+ * If the role is MASTER, another controller instance will be selected
+ * as a candidate master.
+ *
+ * @param nodeId the controller instance identifier
+ * @param deviceId device to revoke mastership role for
+ * @return a mastership event
+ */
+ CompletableFuture<MastershipEvent> setStandby(NodeId nodeId, DeviceId deviceId);
+
+ /**
+ * Allows a controller instance to give up its current role for a device.
+ * If the role is MASTER, another controller instance will be selected
+ * as a candidate master.
+ *
+ * @param nodeId the controller instance identifier
+ * @param deviceId device to revoke mastership role for
+ * @return a mastership event
+ */
+ CompletableFuture<MastershipEvent> relinquishRole(NodeId nodeId, DeviceId deviceId);
+
+ /**
+ * Removes all the roles for the specified controller instance.
+ * If the role was MASTER, another controller instance will be selected
+ * as a candidate master.
+ *
+ * @param nodeId the controller instance identifier
+ */
+ void relinquishAllRole(NodeId nodeId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStoreDelegate.java
new file mode 100644
index 00000000..c71f4ed0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.mastership;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Mastership store delegate abstraction.
+ */
+public interface MastershipStoreDelegate extends StoreDelegate<MastershipEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTerm.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTerm.java
new file mode 100644
index 00000000..049d1d2b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTerm.java
@@ -0,0 +1,71 @@
+/*
+ * 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.mastership;
+
+import java.util.Objects;
+
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.base.MoreObjects;
+
+public final class MastershipTerm {
+
+ private final NodeId master;
+ private final long termNumber;
+
+ private MastershipTerm(NodeId master, long term) {
+ this.master = master;
+ this.termNumber = term;
+ }
+
+ public static MastershipTerm of(NodeId master, long term) {
+ return new MastershipTerm(master, term);
+ }
+
+ public NodeId master() {
+ return master;
+ }
+
+ public long termNumber() {
+ return termNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(master, termNumber);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other instanceof MastershipTerm) {
+ MastershipTerm that = (MastershipTerm) other;
+ return Objects.equals(this.master, that.master) &&
+ Objects.equals(this.termNumber, that.termNumber);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("master", this.master)
+ .add("termNumber", this.termNumber)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTermService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTermService.java
new file mode 100644
index 00000000..1725ee03
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/MastershipTermService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.mastership;
+
+import org.onosproject.net.DeviceId;
+
+// TODO give me a better name
+/**
+ * Service to obtain mastership term information.
+ */
+public interface MastershipTermService {
+
+ // TBD: manage/increment per device mastership change
+ // or increment on any change
+ /**
+ * Returns the term number of mastership change occurred for given device.
+ *
+ * @param deviceId the identifier of the device
+ * @return current master's term.
+ */
+ MastershipTerm getMastershipTerm(DeviceId deviceId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/package-info.java
new file mode 100644
index 00000000..0040680a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/mastership/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.
+ */
+
+/**
+ * Set of abstractions for dealing with controller mastership related topics.
+ */
+package org.onosproject.mastership;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractAnnotated.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractAnnotated.java
new file mode 100644
index 00000000..e0407e27
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractAnnotated.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onosproject.net.DefaultAnnotations.EMPTY;
+
+/**
+ * Base abstraction of an annotated entity.
+ */
+public abstract class AbstractAnnotated implements Annotated {
+
+ private final Annotations annotations;
+
+ // For serialization
+ protected AbstractAnnotated() {
+ this.annotations = null;
+ }
+
+ /**
+ * Creates a new entity, annotated with the specified annotations.
+ *
+ * @param annotations optional key/value annotations map
+ */
+ protected AbstractAnnotated(Annotations... annotations) {
+ checkArgument(annotations.length <= 1, "Only one set of annotations is expected");
+ this.annotations = annotations.length == 1 ? annotations[0] : EMPTY;
+ }
+
+ @Override
+ public Annotations annotations() {
+ return annotations;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractDescription.java
new file mode 100644
index 00000000..d81b83cc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractDescription.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Base implementation of an annotated model description.
+ */
+public abstract class AbstractDescription implements Annotated {
+
+ private static final SparseAnnotations EMPTY = DefaultAnnotations.builder().build();
+
+ private final SparseAnnotations annotations;
+
+ // For serialization
+ protected AbstractDescription() {
+ this.annotations = null;
+ }
+
+ /**
+ * Creates a new entity, annotated with the specified annotations.
+ *
+ * @param annotations optional key/value annotations map
+ */
+ protected AbstractDescription(SparseAnnotations... annotations) {
+ checkArgument(annotations.length <= 1, "Only one set of annotations is expected");
+ this.annotations = annotations.length == 1 ? annotations[0] : EMPTY;
+ }
+
+ @Override
+ public SparseAnnotations annotations() {
+ return annotations;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractElement.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractElement.java
new file mode 100644
index 00000000..595e7b9c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractElement.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Base implementation of network elements, i.e. devices or hosts.
+ */
+public abstract class AbstractElement extends AbstractModel implements Element {
+
+ protected final ElementId id;
+
+ // For serialization
+ public AbstractElement() {
+ id = null;
+ }
+
+ /**
+ * Creates a network element attributed to the specified provider.
+ *
+ * @param providerId identity of the provider
+ * @param id element identifier
+ * @param annotations optional key/value annotations
+ */
+ protected AbstractElement(ProviderId providerId, ElementId id,
+ Annotations... annotations) {
+ super(providerId, annotations);
+ this.id = id;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractModel.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractModel.java
new file mode 100644
index 00000000..f3cd5efe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AbstractModel.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Base implementation of a network model entity.
+ */
+public abstract class AbstractModel extends AbstractAnnotated implements Provided {
+
+ private final ProviderId providerId;
+
+ // For serialization
+ public AbstractModel() {
+ providerId = null;
+ }
+
+ /**
+ * Creates a model entity attributed to the specified provider and
+ * optionally annotated.
+ *
+ * @param providerId identity of the provider
+ * @param annotations optional key/value annotations
+ */
+ protected AbstractModel(ProviderId providerId, Annotations... annotations) {
+ super(annotations);
+ this.providerId = providerId;
+ }
+
+ @Override
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotated.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotated.java
new file mode 100644
index 00000000..ac4545ac
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotated.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Represents an entity that carries arbitrary annotations.
+ */
+public interface Annotated {
+
+ /**
+ * Returns the key/value annotations.
+ *
+ * @return key/value annotations
+ */
+ Annotations annotations();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
new file mode 100644
index 00000000..4949bc40
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+/**
+ * Collection of keys for annotation.
+ * <p>
+ * Number of the annotation keys have been deprecated as the use of annotations
+ * is being phased out and instead network configuration subsystem is being
+ * phased-in for majority of model meta-data.
+ * </p>
+ */
+public final class AnnotationKeys {
+
+ // Prohibit instantiation
+ private AnnotationKeys() {}
+
+ /**
+ * Annotation key for instance name.
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String NAME = "name";
+
+ /**
+ * Annotation key for instance type (e.g. host type).
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String TYPE = "type";
+
+ /**
+ * Annotation key for latitude (e.g. latitude of device).
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String LATITUDE = "latitude";
+
+ /**
+ * Annotation key for longitute (e.g. longitude of device).
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String LONGITUDE = "longitude";
+
+ /**
+ * Annotation key for southbound protocol.
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * Annotation key for the device driver name.
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String DRIVER = "driver";
+
+ /**
+ * Annotation key for durable links.
+ */
+ public static final String DURABLE = "durable";
+
+ /**
+ * Annotation key for latency.
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String LATENCY = "latency";
+
+ /**
+ * Annotation key for bandwidth.
+ * The value for this key is interpreted as Mbps.
+ *
+ * @deprecated since Cardinal
+ */
+ @Deprecated
+ public static final String BANDWIDTH = "bandwidth";
+
+ /**
+ * Annotation key for the number of optical waves.
+ */
+ public static final String OPTICAL_WAVES = "optical.waves";
+
+ /**
+ * Annotation key for the port name.
+ */
+ public static final String PORT_NAME = "portName";
+
+ /**
+ * Annotation key for the router ID.
+ */
+ public static final String ROUTER_ID = "routerId";
+
+ public static final String STATIC_LAMBDA = "staticLambda";
+
+ public static final String STATIC_PORT = "staticPort";
+
+ /**
+ * Annotation key for device location.
+ */
+ public static final String RACK_ADDRESS = "rackAddress";
+
+ /**
+ * Annotation key for device owner.
+ */
+ public static final String OWNER = "owner";
+
+ /**
+ * Returns the value annotated object for the specified annotation key.
+ * The annotated value is expected to be String that can be parsed as double.
+ * If parsing fails, the returned value will be 1.0.
+ *
+ * @param annotated annotated object whose annotated value is obtained
+ * @param key key of annotation
+ * @return double value of annotated object for the specified key
+ */
+ public static double getAnnotatedValue(Annotated annotated, String key) {
+ double value;
+ try {
+ value = Double.parseDouble(annotated.annotations().value(key));
+ } catch (NumberFormatException e) {
+ value = 1.0;
+ }
+ return value;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotations.java
new file mode 100644
index 00000000..4ed70601
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Annotations.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+/**
+ * Represents an set of simple key/value string annotations.
+ */
+public interface Annotations {
+
+ /**
+ * Returns the set of keys for available annotations.
+ *
+ * @return annotation keys
+ */
+ Set<String> keys();
+
+ /**
+ * Returns the value of the specified annotation.
+ *
+ * @param key annotation key
+ * @return annotation value
+ */
+ String value(String key);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationsUtil.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationsUtil.java
new file mode 100644
index 00000000..d43a304d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/AnnotationsUtil.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Utility for processing annotations.
+ */
+public final class AnnotationsUtil {
+
+ public static boolean isEqual(Annotations lhs, Annotations rhs) {
+ if (lhs == rhs) {
+ return true;
+ }
+ if (lhs == null || rhs == null) {
+ return false;
+ }
+
+ if (!lhs.keys().equals(rhs.keys())) {
+ return false;
+ }
+
+ for (String key : lhs.keys()) {
+ if (!lhs.value(key).equals(rhs.value(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // not to be instantiated
+ private AnnotationsUtil() {}
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java
new file mode 100644
index 00000000..4da9d6b3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/ChannelSpacing.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;
+
+import org.onlab.util.Frequency;
+
+/**
+ * Represents interval frequency between two neighboring wavelengths.
+ */
+public enum ChannelSpacing {
+ CHL_100GHZ(100), // 100 GHz
+ CHL_50GHZ(50), // 50 GHz
+ CHL_25GHZ(25), // 25 GHz
+ CHL_12P5GHZ(12.5), // 12.5 GHz
+ CHL_6P25GHZ(6.5); // 6.25 GHz
+
+ private final Frequency frequency;
+
+ /**
+ * Creates an instance with the specified interval in GHz.
+ *
+ * @param value interval of neighboring wavelengths in GHz.
+ */
+ ChannelSpacing(double value) {
+ this.frequency = Frequency.ofGHz(value);
+ }
+
+ public Frequency frequency() {
+ return frequency;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/ConnectPoint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
new file mode 100644
index 00000000..0d13f4aa
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
@@ -0,0 +1,175 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of a network connection point expressed as a pair of the
+ * network element identifier and port number.
+ */
+public class ConnectPoint {
+
+ private final ElementId elementId;
+ private final PortNumber portNumber;
+
+ /**
+ * Creates a new connection point.
+ *
+ * @param elementId network element identifier
+ * @param portNumber port number
+ */
+ public ConnectPoint(ElementId elementId, PortNumber portNumber) {
+ this.elementId = elementId;
+ this.portNumber = portNumber;
+ }
+
+ /**
+ * Returns the network element identifier.
+ *
+ * @return element identifier
+ */
+ public ElementId elementId() {
+ return elementId;
+ }
+
+ /**
+ * Returns the identifier of the infrastructure device if the connection
+ * point belongs to a network element which is indeed an infrastructure
+ * device.
+ *
+ * @return network element identifier as a device identifier
+ * @throws java.lang.IllegalStateException if connection point is not
+ * associated with a device
+ */
+ public DeviceId deviceId() {
+ if (elementId instanceof DeviceId) {
+ return (DeviceId) elementId;
+ }
+ throw new IllegalStateException("Connection point not associated " +
+ "with an infrastructure device");
+ }
+
+ /**
+ * Returns the identifier of the infrastructure device if the connection
+ * point belongs to a network element which is indeed an end-station host.
+ *
+ * @return network element identifier as a host identifier
+ * @throws java.lang.IllegalStateException if connection point is not
+ * associated with a host
+ */
+ public HostId hostId() {
+ if (elementId instanceof HostId) {
+ return (HostId) elementId;
+ }
+ throw new IllegalStateException("Connection point not associated " +
+ "with an end-station host");
+ }
+
+ /**
+ * Returns the identifier of the infrastructure device if the connection
+ * point belongs to a network element which is indeed an ip of pcc
+ * client identifier.
+ *
+ * @return network element identifier as a pcc client identifier
+ * @throws java.lang.IllegalStateException if connection point is not
+ * associated with a pcc client
+ */
+ public IpElementId ipElementId() {
+ if (elementId instanceof IpElementId) {
+ return (IpElementId) elementId;
+ }
+ throw new IllegalStateException("Connection point not associated " +
+ "with an pcc client");
+ }
+
+ /**
+ * Returns the connection port number.
+ *
+ * @return port number
+ */
+ public PortNumber port() {
+ return portNumber;
+ }
+
+ /**
+ * Parse a device connect point from a string.
+ * The connect point should be in the format "deviceUri/portNumber".
+ *
+ * @param string string to parse
+ * @return a ConnectPoint based on the information in the string.
+ */
+ public static ConnectPoint deviceConnectPoint(String string) {
+ checkNotNull(string);
+ String[] splitted = string.split("/");
+ checkArgument(splitted.length == 2,
+ "Connect point must be in \"deviceUri/portNumber\" format");
+
+ return new ConnectPoint(DeviceId.deviceId(splitted[0]),
+ PortNumber.portNumber(splitted[1]));
+ }
+
+ /**
+ * Parse a host connect point from a string.
+ * The connect point should be in the format "hostId/vlanId/portNumber".
+ *
+ * @param string string to parse
+ * @return a ConnectPoint based on the information in the string.
+ */
+ public static ConnectPoint hostConnectPoint(String string) {
+ checkNotNull(string);
+ String[] splitted = string.split("/");
+ checkArgument(splitted.length == 3,
+ "Connect point must be in \"hostId/vlanId/portNumber\" format");
+
+ int lastSlash = string.lastIndexOf("/");
+
+ return new ConnectPoint(HostId.hostId(string.substring(0, lastSlash)),
+ PortNumber.portNumber(string.substring(lastSlash + 1, string.length())));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(elementId, portNumber);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ConnectPoint) {
+ final ConnectPoint other = (ConnectPoint) obj;
+ return Objects.equals(this.elementId, other.elementId) &&
+ Objects.equals(this.portNumber, other.portNumber);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("elementId", elementId)
+ .add("portNumber", portNumber)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultAnnotations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultAnnotations.java
new file mode 100644
index 00000000..7c97ecdd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultAnnotations.java
@@ -0,0 +1,254 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import com.google.common.collect.Maps;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a set of simple annotations that can be used to add arbitrary
+ * attributes to various parts of the data model.
+ */
+public final class DefaultAnnotations implements SparseAnnotations {
+
+ public static final SparseAnnotations EMPTY = DefaultAnnotations.builder().build();
+
+ private final Map<String, String> map;
+
+ // For serialization
+ private DefaultAnnotations() {
+ this.map = null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DefaultAnnotations that = (DefaultAnnotations) o;
+
+ return Objects.equals(this.map, that.map);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.map);
+ }
+
+ /**
+ * Returns the annotations as a map.
+ *
+ * @return a copy of the contents of the annotations as a map.
+ */
+ public HashMap<String, String> asMap() {
+ return Maps.newHashMap(this.map);
+ }
+
+ /**
+ * Creates a new set of annotations using clone of the specified hash map.
+ *
+ * @param map hash map of key/value pairs
+ */
+ private DefaultAnnotations(Map<String, String> map) {
+ this.map = map;
+ }
+
+ /**
+ * Creates a new annotations builder.
+ *
+ * @return new annotations builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Merges the specified base set of annotations and additional sparse
+ * annotations into new combined annotations. If the supplied sparse
+ * annotations are empty, the original base annotations are returned.
+ * Any keys tagged for removal in the sparse annotations will be omitted
+ * in the resulting merged annotations.
+ *
+ * @param annotations base annotations
+ * @param sparseAnnotations additional sparse annotations
+ * @return combined annotations or the original base annotations if there
+ * are not additional annotations
+ */
+ public static DefaultAnnotations merge(DefaultAnnotations annotations,
+ SparseAnnotations sparseAnnotations) {
+ checkNotNull(annotations, "Annotations cannot be null");
+ if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
+ return annotations;
+ }
+
+ // Merge the two maps. Yes, this is not very efficient, but the
+ // use-case implies small maps and infrequent merges, so we opt for
+ // simplicity.
+ Map<String, String> merged = copy(annotations.map);
+ for (String key : sparseAnnotations.keys()) {
+ if (sparseAnnotations.isRemoved(key)) {
+ merged.remove(key);
+ } else {
+ merged.put(key, sparseAnnotations.value(key));
+ }
+ }
+ return new DefaultAnnotations(merged);
+ }
+
+ /**
+ * Creates the union of two given SparseAnnotations.
+ * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
+ * result will be {@link SparseAnnotations} instead of {@link Annotations}.
+ *
+ * A key tagged for removal will remain in the output SparseAnnotations,
+ * if the counterpart of the input does not contain the same key.
+ *
+ * @param annotations base annotations
+ * @param sparseAnnotations additional sparse annotations
+ * @return combined annotations or the original base annotations if there
+ * are not additional annotations
+ */
+ public static SparseAnnotations union(SparseAnnotations annotations,
+ SparseAnnotations sparseAnnotations) {
+
+ if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
+ return annotations;
+ }
+
+ final HashMap<String, String> newMap;
+ if (annotations instanceof DefaultAnnotations) {
+ newMap = copy(((DefaultAnnotations) annotations).map);
+ } else {
+ newMap = new HashMap<>(annotations.keys().size() +
+ sparseAnnotations.keys().size());
+ putAllSparseAnnotations(newMap, annotations);
+ }
+
+ putAllSparseAnnotations(newMap, sparseAnnotations);
+ return new DefaultAnnotations(newMap);
+ }
+
+ // adds the key-values contained in sparseAnnotations to
+ // newMap, if sparseAnnotations had a key tagged for removal,
+ // and corresponding key exist in newMap, entry will be removed.
+ // if corresponding key does not exist, removal tag will be added to
+ // the newMap.
+ private static void putAllSparseAnnotations(
+ final HashMap<String, String> newMap,
+ SparseAnnotations sparseAnnotations) {
+
+ for (String key : sparseAnnotations.keys()) {
+ if (sparseAnnotations.isRemoved(key)) {
+ if (newMap.containsKey(key)) {
+ newMap.remove(key);
+ } else {
+ newMap.put(key, Builder.REMOVED);
+ }
+ } else {
+ String value = sparseAnnotations.value(key);
+ newMap.put(key, value);
+ }
+ }
+ }
+
+ @Override
+ public Set<String> keys() {
+ return Collections.unmodifiableSet(map.keySet());
+ }
+
+ @Override
+ public String value(String key) {
+ String value = map.get(key);
+ return Objects.equals(Builder.REMOVED, value) ? null : value;
+ }
+
+ @Override
+ public boolean isRemoved(String key) {
+ return Objects.equals(Builder.REMOVED, map.get(key));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static HashMap<String, String> copy(Map<String, String> original) {
+ if (original instanceof HashMap) {
+ return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
+ }
+ throw new IllegalArgumentException("Expecting HashMap instance");
+ }
+
+ @Override
+ public String toString() {
+ return (map == null) ? "null" : map.toString();
+ }
+
+ /**
+ * Facility for gradually building model annotations.
+ */
+ public static final class Builder {
+
+ private static final String REMOVED = "~rEmOvEd~";
+ private final Map<String, String> builder = new HashMap<>();
+
+ // Private construction is forbidden.
+ private Builder() {
+ }
+
+ /**
+ * Adds the specified annotation. Any previous value associated with
+ * the given annotation key will be overwritten.
+ *
+ * @param key annotation key
+ * @param value annotation value
+ * @return self
+ */
+ public Builder set(String key, String value) {
+ builder.put(key, value);
+ return this;
+ }
+
+ /**
+ * Adds the specified annotation. Any previous value associated with
+ * the given annotation key will be tagged for removal.
+ *
+ * @param key annotation key
+ * @return self
+ */
+ public Builder remove(String key) {
+ builder.put(key, REMOVED);
+ return this;
+ }
+
+ /**
+ * Returns immutable annotations built from the accrued key/values pairs.
+ *
+ * @return annotations
+ */
+ public DefaultAnnotations build() {
+ return new DefaultAnnotations(copy(builder));
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultDevice.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultDevice.java
new file mode 100644
index 00000000..f3f0fe74
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultDevice.java
@@ -0,0 +1,142 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default infrastructure device model implementation.
+ */
+public class DefaultDevice extends AbstractElement implements Device {
+
+ private final Type type;
+ private final String manufacturer;
+ private final String serialNumber;
+ private final String hwVersion;
+ private final String swVersion;
+ private final ChassisId chassisId;
+
+ // For serialization
+ private DefaultDevice() {
+ this.type = null;
+ this.manufacturer = null;
+ this.hwVersion = null;
+ this.swVersion = null;
+ this.serialNumber = null;
+ this.chassisId = null;
+ }
+
+ /**
+ * Creates a network element attributed to the specified provider.
+ *
+ * @param providerId identity of the provider
+ * @param id device identifier
+ * @param type device type
+ * @param manufacturer device manufacturer
+ * @param hwVersion device HW version
+ * @param swVersion device SW version
+ * @param serialNumber device serial number
+ * @param chassisId chassis id
+ * @param annotations optional key/value annotations
+ */
+ public DefaultDevice(ProviderId providerId, DeviceId id, Type type,
+ String manufacturer, String hwVersion, String swVersion,
+ String serialNumber, ChassisId chassisId,
+ Annotations... annotations) {
+ super(providerId, id, annotations);
+ this.type = type;
+ this.manufacturer = manufacturer;
+ this.hwVersion = hwVersion;
+ this.swVersion = swVersion;
+ this.serialNumber = serialNumber;
+ this.chassisId = chassisId;
+ }
+
+ @Override
+ public DeviceId id() {
+ return (DeviceId) id;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public String manufacturer() {
+ return manufacturer;
+ }
+
+ @Override
+ public String hwVersion() {
+ return hwVersion;
+ }
+
+ @Override
+ public String swVersion() {
+ return swVersion;
+ }
+
+ @Override
+ public String serialNumber() {
+ return serialNumber;
+ }
+
+ @Override
+ public ChassisId chassisId() {
+ return chassisId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, type, manufacturer, hwVersion, swVersion, serialNumber);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultDevice) {
+ final DefaultDevice other = (DefaultDevice) obj;
+ return Objects.equals(this.id, other.id) &&
+ Objects.equals(this.type, other.type) &&
+ Objects.equals(this.manufacturer, other.manufacturer) &&
+ Objects.equals(this.hwVersion, other.hwVersion) &&
+ Objects.equals(this.swVersion, other.swVersion) &&
+ Objects.equals(this.serialNumber, other.serialNumber);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("id", id)
+ .add("type", type)
+ .add("manufacturer", manufacturer)
+ .add("hwVersion", hwVersion)
+ .add("swVersion", swVersion)
+ .add("serialNumber", serialNumber)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultEdgeLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultEdgeLink.java
new file mode 100644
index 00000000..67ceef7a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultEdgeLink.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default edge link model implementation.
+ */
+public class DefaultEdgeLink extends DefaultLink implements EdgeLink {
+
+ private final HostId hostId;
+ private final HostLocation hostLocation;
+
+ /**
+ * Creates an edge link using the supplied information.
+ *
+ * @param providerId provider identity
+ * @param hostPoint host-side connection point
+ * @param hostLocation location where host attaches to the network
+ * @param isIngress true to indicate host-to-network direction; false
+ * for network-to-host direction
+ * @param annotations optional key/value annotations
+ */
+ public DefaultEdgeLink(ProviderId providerId, ConnectPoint hostPoint,
+ HostLocation hostLocation, boolean isIngress,
+ Annotations... annotations) {
+ super(providerId, isIngress ? hostPoint : hostLocation,
+ isIngress ? hostLocation : hostPoint, Type.EDGE, annotations);
+ checkArgument(hostPoint.elementId() instanceof HostId,
+ "Host point does not refer to a host ID");
+ this.hostId = (HostId) hostPoint.elementId();
+ this.hostLocation = hostLocation;
+ }
+
+ @Override
+ public HostId hostId() {
+ return hostId;
+ }
+
+ @Override
+ public HostLocation hostLocation() {
+ return hostLocation;
+ }
+
+ /**
+ * Creates a phantom edge link, to an unspecified end-station. This link
+ * does not represent any actually discovered link stored in the system.
+ *
+ * @param edgePort network edge port
+ * @param isIngress true to indicate host-to-network direction; false
+ * for network-to-host direction
+ * @return new phantom edge link
+ */
+ public static DefaultEdgeLink createEdgeLink(ConnectPoint edgePort,
+ boolean isIngress) {
+ checkNotNull(edgePort, "Edge port cannot be null");
+ HostLocation location = (edgePort instanceof HostLocation) ?
+ (HostLocation) edgePort : new HostLocation(edgePort, 0);
+ return new DefaultEdgeLink(ProviderId.NONE,
+ new ConnectPoint(HostId.NONE, PortNumber.P0),
+ location, isIngress);
+ }
+
+ /**
+ * Creates a an edge link, to the specified end-station.
+ *
+ * @param host host
+ * @param isIngress true to indicate host-to-network direction; false
+ * for network-to-host direction
+ * @return new phantom edge link
+ */
+ public static DefaultEdgeLink createEdgeLink(Host host, boolean isIngress) {
+ checkNotNull(host, "Host cannot be null");
+ return new DefaultEdgeLink(ProviderId.NONE,
+ new ConnectPoint(host.id(), PortNumber.P0),
+ host.location(), isIngress);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultHost.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultHost.java
new file mode 100644
index 00000000..2877701e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultHost.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * A basic implementation of a Host.
+ */
+public class DefaultHost extends AbstractElement implements Host {
+
+ private final MacAddress mac;
+ private final VlanId vlan;
+ private final HostLocation location;
+ private final Set<IpAddress> ips;
+
+ /**
+ * Creates an end-station host using the supplied information.
+ *
+ * @param providerId provider identity
+ * @param id host identifier
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param location host location
+ * @param ips host IP addresses
+ * @param annotations optional key/value annotations
+ */
+ public DefaultHost(ProviderId providerId, HostId id, MacAddress mac,
+ VlanId vlan, HostLocation location, Set<IpAddress> ips,
+ Annotations... annotations) {
+ super(providerId, id, annotations);
+ this.mac = mac;
+ this.vlan = vlan;
+ this.location = location;
+ this.ips = new HashSet<>(ips);
+ }
+
+ @Override
+ public HostId id() {
+ return (HostId) id;
+ }
+
+ @Override
+ public MacAddress mac() {
+ return mac;
+ }
+
+ @Override
+ public Set<IpAddress> ipAddresses() {
+ return Collections.unmodifiableSet(ips);
+ }
+
+ @Override
+ public HostLocation location() {
+ return location;
+ }
+
+ @Override
+ public VlanId vlan() {
+ return vlan;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, mac, vlan, location);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultHost) {
+ final DefaultHost other = (DefaultHost) obj;
+ return Objects.equals(this.id, other.id) &&
+ Objects.equals(this.mac, other.mac) &&
+ Objects.equals(this.vlan, other.vlan) &&
+ Objects.equals(this.location, other.location);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("id", id)
+ .add("mac", mac)
+ .add("vlan", vlan)
+ .add("location", location)
+ .add("ipAddresses", ips)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultLink.java
new file mode 100644
index 00000000..4d1ca6de
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultLink.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onosproject.net.Link.State.ACTIVE;
+
+/**
+ * Default infrastructure link model implementation.
+ */
+public class DefaultLink extends AbstractModel implements Link {
+
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+ private final Type type;
+ private final State state;
+ private final boolean isDurable;
+
+ /**
+ * Creates an active infrastructure link using the supplied information.
+ *
+ * @param providerId provider identity
+ * @param src link source
+ * @param dst link destination
+ * @param type link type
+ * @param annotations optional key/value annotations
+ */
+ public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
+ Type type, Annotations... annotations) {
+ this(providerId, src, dst, type, ACTIVE, false, annotations);
+ }
+
+ /**
+ * Creates an infrastructure link using the supplied information.
+ * Links marked as durable will remain in the inventory when a vanish
+ * message is received and instead will be marked as inactive.
+ *
+ * @param providerId provider identity
+ * @param src link source
+ * @param dst link destination
+ * @param type link type
+ * @param state link state
+ * @param isDurable indicates if the link is to be considered durable
+ * @param annotations optional key/value annotations
+ */
+ public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
+ Type type, State state,
+ boolean isDurable, Annotations... annotations) {
+ super(providerId, annotations);
+ this.src = src;
+ this.dst = dst;
+ this.type = type;
+ this.state = state;
+ this.isDurable = isDurable;
+ }
+
+ @Override
+ public ConnectPoint src() {
+ return src;
+ }
+
+ @Override
+ public ConnectPoint dst() {
+ return dst;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public State state() {
+ return state;
+ }
+
+ @Override
+ public boolean isDurable() {
+ return isDurable;
+ }
+
+ // Note: Durability & state are purposefully omitted form equality & hashCode.
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(src, dst, type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultLink) {
+ final DefaultLink other = (DefaultLink) obj;
+ return Objects.equals(this.src, other.src) &&
+ Objects.equals(this.dst, other.dst) &&
+ Objects.equals(this.type, other.type);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("src", src)
+ .add("dst", dst)
+ .add("type", type)
+ .add("state", state)
+ .add("durable", isDurable)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPath.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPath.java
new file mode 100644
index 00000000..a4789cac
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPath.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of a network path.
+ */
+public class DefaultPath extends DefaultLink implements Path {
+
+ private final List<Link> links;
+ private final double cost;
+
+ /**
+ * Creates a path from the specified source and destination using the
+ * supplied list of links.
+ *
+ * @param providerId provider identity
+ * @param links contiguous links that comprise the path
+ * @param cost unit-less path cost
+ * @param annotations optional key/value annotations
+ */
+ public DefaultPath(ProviderId providerId, List<Link> links, double cost,
+ Annotations... annotations) {
+ super(providerId, source(links), destination(links), Type.INDIRECT, annotations);
+ this.links = ImmutableList.copyOf(links);
+ this.cost = cost;
+ }
+
+ @Override
+ public List<Link> links() {
+ return links;
+ }
+
+ @Override
+ public double cost() {
+ return cost;
+ }
+
+ // Returns the source of the first link.
+ private static ConnectPoint source(List<Link> links) {
+ checkNotNull(links, "List of path links cannot be null");
+ checkArgument(!links.isEmpty(), "List of path links cannot be empty");
+ return links.get(0).src();
+ }
+
+ // Returns the destination of the last link.
+ private static ConnectPoint destination(List<Link> links) {
+ checkNotNull(links, "List of path links cannot be null");
+ checkArgument(!links.isEmpty(), "List of path links cannot be empty");
+ return links.get(links.size() - 1).dst();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("src", src())
+ .add("dst", dst())
+ .add("type", type())
+ .add("state", state())
+ .add("durable", isDurable())
+ .add("links", links)
+ .add("cost", cost)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(links);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultPath) {
+ final DefaultPath other = (DefaultPath) obj;
+ return Objects.equals(this.links, other.links);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPort.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPort.java
new file mode 100644
index 00000000..a6b8441a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DefaultPort.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default port implementation.
+ */
+public class DefaultPort extends AbstractAnnotated implements Port {
+
+ /** Default port speed in Mbps. */
+ public static final long DEFAULT_SPEED = 1_000;
+
+ private final Element element;
+ private final PortNumber number;
+ private final boolean isEnabled;
+ private final Type type;
+ private final long portSpeed;
+
+ /**
+ * Creates a network element attributed to the specified provider.
+ *
+ * @param element parent network element
+ * @param number port number
+ * @param isEnabled indicator whether the port is up and active
+ * @param annotations optional key/value annotations
+ */
+ public DefaultPort(Element element, PortNumber number, boolean isEnabled,
+ Annotations... annotations) {
+ this(element, number, isEnabled, Type.COPPER, DEFAULT_SPEED, annotations);
+ }
+
+ /**
+ * Creates a network element attributed to the specified provider.
+ *
+ * @param element parent network element
+ * @param number port number
+ * @param isEnabled indicator whether the port is up and active
+ * @param type port type
+ * @param portSpeed port speed in Mbs
+ * @param annotations optional key/value annotations
+ */
+ public DefaultPort(Element element, PortNumber number, boolean isEnabled,
+ Type type, long portSpeed, Annotations... annotations) {
+ super(annotations);
+ this.element = element;
+ this.number = number;
+ this.isEnabled = isEnabled;
+ this.type = type;
+ this.portSpeed = portSpeed;
+ }
+
+ @Override
+ public Element element() {
+ return element;
+ }
+
+ @Override
+ public PortNumber number() {
+ return number;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public long portSpeed() {
+ return portSpeed;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, isEnabled, type, portSpeed, annotations());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultPort) {
+ final DefaultPort other = (DefaultPort) obj;
+ return Objects.equals(this.element.id(), other.element.id()) &&
+ Objects.equals(this.number, other.number) &&
+ Objects.equals(this.isEnabled, other.isEnabled) &&
+ Objects.equals(this.type, other.type) &&
+ Objects.equals(this.portSpeed, other.portSpeed) &&
+ Objects.equals(this.annotations(), other.annotations());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("element", element.id())
+ .add("number", number)
+ .add("isEnabled", isEnabled)
+ .add("type", type)
+ .add("portSpeed", portSpeed)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Description.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Description.java
new file mode 100644
index 00000000..c01af549
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Description.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Base abstraction of a piece of information about network elements.
+ */
+public interface Description extends Annotated {
+
+ @Override
+ SparseAnnotations annotations();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Device.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Device.java
new file mode 100644
index 00000000..d9001825
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Device.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import org.onlab.packet.ChassisId;
+
+/**
+ * Representation of a network infrastructure device.
+ */
+public interface Device extends Element {
+
+ /**
+ * Coarse classification of the type of the infrastructure device.
+ */
+ public enum Type {
+ SWITCH, ROUTER, ROADM, OTN, ROADM_OTN, FIREWALL, BALANCER, IPS, IDS, CONTROLLER,
+ VIRTUAL, FIBER_SWITCH, MICROWAVE, OTHER
+ }
+
+ /**
+ * Returns the device identifier.
+ *
+ * @return device id
+ */
+ @Override
+ DeviceId id();
+
+ /**
+ * Returns the type of the infrastructure device.
+ *
+ * @return type of the device
+ */
+ Type type();
+
+ /**
+ * Returns the device manufacturer name.
+ *
+ * @return manufacturer name
+ */
+ String manufacturer();
+
+ /**
+ * Returns the device hardware version.
+ *
+ * @return hardware version
+ */
+ String hwVersion();
+
+ /**
+ * Returns the device software version.
+ *
+ * @return software version
+ */
+ String swVersion();
+
+ /**
+ * Returns the device serial number.
+ *
+ * @return serial number
+ */
+ String serialNumber();
+
+ /**
+ * Returns the device chassis id.
+ *
+ * @return chassis id
+ */
+ ChassisId chassisId();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/DeviceId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DeviceId.java
new file mode 100644
index 00000000..5331342e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/DeviceId.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import java.net.URI;
+import java.util.Objects;
+
+/**
+ * Immutable representation of a device identity.
+ */
+public final class DeviceId extends ElementId {
+
+ /**
+ * Represents either no device, or an unspecified device.
+ */
+ public static final DeviceId NONE = deviceId("none:none");
+
+ private final URI uri;
+ private final String str;
+
+ // Public construction is prohibited
+ private DeviceId(URI uri) {
+ this.uri = uri;
+ this.str = uri.toString().toLowerCase();
+ }
+
+
+ // Default constructor for serialization
+ protected DeviceId() {
+ this.uri = null;
+ this.str = null;
+ }
+
+ /**
+ * Creates a device id using the supplied URI.
+ *
+ * @param uri device URI
+ * @return DeviceId
+ */
+ public static DeviceId deviceId(URI uri) {
+ return new DeviceId(uri);
+ }
+
+ /**
+ * Creates a device id using the supplied URI string.
+ *
+ * @param string device URI string
+ * @return DeviceId
+ */
+ public static DeviceId deviceId(String string) {
+ return deviceId(URI.create(string));
+ }
+
+ /**
+ * Returns the backing URI.
+ *
+ * @return backing URI
+ */
+ public URI uri() {
+ return uri;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(str);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DeviceId) {
+ final DeviceId that = (DeviceId) obj;
+ return this.getClass() == that.getClass() &&
+ Objects.equals(this.str, that.str);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return str;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/EdgeLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/EdgeLink.java
new file mode 100644
index 00000000..73b916b2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/EdgeLink.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of a link between an end-station host and the network
+ * infrastructure.
+ */
+public interface EdgeLink extends Link {
+
+ /**
+ * Returns the host identification.
+ *
+ * @return host identifier
+ */
+ HostId hostId();
+
+ /**
+ * Returns the connection point where the host attaches to the
+ * network infrastructure.
+ *
+ * @return host location point
+ */
+ HostLocation hostLocation();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Element.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Element.java
new file mode 100644
index 00000000..6cdab9e4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Element.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Base abstraction of a network element, i.e. an infrastructure device or an end-station host.
+ */
+public interface Element extends Annotated, Provided {
+
+ /**
+ * Returns the network element identifier.
+ *
+ * @return element identifier
+ */
+ ElementId id();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/ElementId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/ElementId.java
new file mode 100644
index 00000000..87defb26
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/ElementId.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * Immutable representation of a network element identity.
+ */
+public abstract class ElementId implements NetworkResource {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/GridType.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/GridType.java
new file mode 100644
index 00000000..8e9bc5b9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/GridType.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Represents type of wavelength grid.
+ *
+ * <p>
+ * See Open Networking Foundation "Optical Transport Protocol Extensions Version 1.0".
+ * </p>
+ */
+public enum GridType {
+ DWDM, // Dense Wavelength Division Multiplexing
+ CWDM, // Coarse WDM
+ FLEX // Flex Grid
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Host.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Host.java
new file mode 100644
index 00000000..646f2283
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Host.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import java.util.Set;
+
+/**
+ * Abstraction of an end-station host on the network, essentially a NIC.
+ */
+public interface Host extends Element {
+
+ /**
+ * Host identification.
+ *
+ * @return host id
+ */
+ @Override
+ HostId id();
+
+ /**
+ * Returns the host MAC address.
+ *
+ * @return mac address
+ */
+ MacAddress mac();
+
+ /**
+ * Returns the VLAN ID tied to this host.
+ *
+ * @return VLAN ID value
+ */
+ VlanId vlan();
+
+ /**
+ * Returns set of IP addresses currently bound to the host MAC address.
+ *
+ * @return set of IP addresses; empty if no IP address is bound
+ */
+ Set<IpAddress> ipAddresses();
+
+ /**
+ * Returns the most recent host location where the host attaches to the
+ * network edge.
+ *
+ * @return host location
+ */
+ HostLocation location();
+
+ // TODO: explore capturing list of recent locations to aid in mobility
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/HostId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/HostId.java
new file mode 100644
index 00000000..3e0d2b24
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/HostId.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Immutable representation of a host identity.
+ */
+public final class HostId extends ElementId {
+
+ /**
+ * Represents either no host, or an unspecified host; used for creating
+ * open ingress/egress edge links.
+ */
+ public static final HostId NONE = new HostId(MacAddress.ZERO, VlanId.NONE);
+
+ private static final int MAC_LENGTH = 17;
+ private static final int MIN_ID_LENGTH = 19;
+
+ private final MacAddress mac;
+ private final VlanId vlanId;
+
+ // Public construction is prohibited
+ private HostId(MacAddress mac, VlanId vlanId) {
+ this.mac = mac;
+ this.vlanId = vlanId;
+ }
+
+ // Default constructor for serialization
+ private HostId() {
+ this.mac = null;
+ this.vlanId = null;
+ }
+
+ /**
+ * Returns the host MAC address.
+ *
+ * @return MAC address
+ */
+ public MacAddress mac() {
+ return mac;
+ }
+
+ /**
+ * Returns the host vlan Id.
+ *
+ * @return vlan Id
+ */
+ public VlanId vlanId() {
+ return vlanId;
+ }
+
+ /**
+ * Creates a device id using the supplied ID string.
+ *
+ * @param string device URI string
+ * @return host identifier
+ */
+ public static HostId hostId(String string) {
+ checkArgument(string.length() >= MIN_ID_LENGTH,
+ "Host ID must be at least %s characters", MIN_ID_LENGTH);
+ MacAddress mac = MacAddress.valueOf(string.substring(0, MAC_LENGTH));
+ VlanId vlanId = VlanId.vlanId(Short.parseShort(string.substring(MAC_LENGTH + 1)));
+ return new HostId(mac, vlanId);
+ }
+
+ /**
+ * Creates a device id using the supplied MAC &amp; VLAN ID.
+ *
+ * @param mac mac address
+ * @param vlanId vlan identifier
+ * @return host identifier
+ */
+ public static HostId hostId(MacAddress mac, VlanId vlanId) {
+ return new HostId(mac, vlanId);
+ }
+
+ /**
+ * Creates a device id using the supplied MAC and default VLAN.
+ *
+ * @param mac mac address
+ * @return host identifier
+ */
+ public static HostId hostId(MacAddress mac) {
+ return hostId(mac, VlanId.vlanId(VlanId.UNTAGGED));
+ }
+
+ public String toString() {
+ return mac + "/" + vlanId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mac, vlanId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof HostId) {
+ final HostId other = (HostId) obj;
+ return Objects.equals(this.mac, other.mac) &&
+ Objects.equals(this.vlanId, other.vlanId);
+ }
+ return false;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/HostLocation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/HostLocation.java
new file mode 100644
index 00000000..b0553a2b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/HostLocation.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import static org.onosproject.net.PortNumber.P0;
+
+/**
+ * Representation of a network edge location where an end-station host is
+ * connected.
+ */
+public class HostLocation extends ConnectPoint {
+
+ /**
+ * Represents a no location or an unknown location.
+ */
+ public static final HostLocation NONE = new HostLocation(DeviceId.NONE, P0, 0L);
+
+ // Note that time is explicitly excluded from the notion of equality.
+ private final long time;
+
+ /**
+ * Creates a new host location using the supplied device &amp; port.
+ *
+ * @param deviceId device identity
+ * @param portNumber device port number
+ * @param time time when detected, in millis since start of epoch
+ */
+ public HostLocation(DeviceId deviceId, PortNumber portNumber, long time) {
+ super(deviceId, portNumber);
+ this.time = time;
+ }
+
+ /**
+ * Creates a new host location derived from the supplied connection point.
+ *
+ * @param connectPoint connection point
+ * @param time time when detected, in millis since start of epoch
+ */
+ public HostLocation(ConnectPoint connectPoint, long time) {
+ super(connectPoint.deviceId(), connectPoint.port());
+ this.time = time;
+ }
+
+ /**
+ * Returns the time when the location was established, given in
+ * milliseconds since start of epoch.
+ *
+ * @return time in milliseconds since start of epoch
+ */
+ public long time() {
+ return time;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/IndexedLambda.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/IndexedLambda.java
new file mode 100644
index 00000000..6b5fa652
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/IndexedLambda.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Implementation of Lambda simply designated by an index number of wavelength.
+ */
+public class IndexedLambda implements Lambda {
+
+ private final long index;
+
+ /**
+ * Creates an instance representing the wavelength specified by the given index number.
+ * It is recommended to use {@link Lambda#indexedLambda(long)} unless you want to use the
+ * concrete type, IndexedLambda, directly.
+ *
+ * @param index index number of wavelength
+ */
+ public IndexedLambda(long index) {
+ this.index = index;
+ }
+
+ /**
+ * Returns the index number of lambda.
+ *
+ * @return the index number of lambda
+ */
+ public long index() {
+ return index;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(index);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof IndexedLambda)) {
+ return false;
+ }
+
+ final IndexedLambda that = (IndexedLambda) obj;
+ return this.index == that.index;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lambda", index)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/IpElementId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/IpElementId.java
new file mode 100644
index 00000000..9e19b624
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/IpElementId.java
@@ -0,0 +1,61 @@
+package org.onosproject.net;
+
+import java.util.Objects;
+import org.onlab.packet.IpAddress;
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represent for a Element ID using ip address.
+ */
+public final class IpElementId extends ElementId {
+
+ private final IpAddress ipAddress;
+
+ /**
+ * Public construction is prohibited.
+ * @param ipAddress ip address
+ */
+ private IpElementId(IpAddress ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ /**
+ * Create a IP Element ID.
+ * @param ipAddress IP address
+ * @return IpElementId
+ */
+ public static IpElementId ipElement(IpAddress ipAddress) {
+ return new IpElementId(ipAddress);
+ }
+
+ /**
+ * Returns the ip address.
+ *
+ * @return ipAddress
+ */
+ public IpAddress ipAddress() {
+ return ipAddress;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipAddress);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IpElementId) {
+ final IpElementId other = (IpElementId) obj;
+ return Objects.equals(this.ipAddress, other.ipAddress);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("ipAddress", ipAddress).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Lambda.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Lambda.java
new file mode 100644
index 00000000..47d0d5f0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Lambda.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of wavelength. Currently, this is just a marker interface
+ */
+public interface Lambda {
+ /**
+ * Create an Lambda instance with the specified wavelength index number.
+ *
+ * @param lambda index number
+ * @return an instance
+ */
+ static Lambda indexedLambda(long lambda) {
+ return new IndexedLambda(lambda);
+ }
+
+ /**
+ * Creates a Lambda instance with the specified arguments.
+ *
+ * @param gridType grid type
+ * @param channelSpacing channel spacing
+ * @param spacingMultiplier channel spacing multiplier
+ * @param slotGranularity slot width granularity
+ * @return new lambda with specified arguments
+ */
+ static Lambda ochSignal(GridType gridType, ChannelSpacing channelSpacing,
+ int spacingMultiplier, int slotGranularity) {
+ return new OchSignal(gridType, channelSpacing, spacingMultiplier, slotGranularity);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Link.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Link.java
new file mode 100644
index 00000000..7541f751
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Link.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of a network infrastructure link.
+ */
+public interface Link extends Annotated, Provided, NetworkResource {
+
+ /**
+ * Coarse representation of the link type.
+ */
+ enum Type {
+ /**
+ * Signifies that this is a direct single-segment link.
+ */
+ DIRECT,
+
+ /**
+ * Signifies that this link is potentially comprised from multiple
+ * underlying segments or hops, and as such should be used to tag
+ * links traversing optical paths, tunnels or intervening 'dark'
+ * switches.
+ */
+ INDIRECT,
+
+ /**
+ * Signifies that this link is an edge, i.e. host link.
+ */
+ EDGE,
+
+ /**
+ * Signifies that this link represents a logical link backed by
+ * some form of a tunnel, e.g., GRE, MPLS, ODUk, OCH.
+ */
+ TUNNEL,
+
+ /**
+ * Signifies that this link is realized by fiber (either single channel or WDM).
+ */
+ OPTICAL,
+
+ /**
+ * Signifies that this link is a virtual link or a pseudo-wire.
+ */
+ VIRTUAL
+ }
+
+ /**
+ * Representation of the link state, which applies primarily only to
+ * configured durable links, i.e. those that need to remain present,
+ * but instead be marked as inactive.
+ */
+ enum State {
+ /**
+ * Signifies that a link is currently active.
+ */
+ ACTIVE,
+
+ /**
+ * Signifies that a link is currently active.
+ */
+ INACTIVE
+ }
+
+ /**
+ * Returns the link source connection point.
+ *
+ * @return link source connection point
+ */
+ ConnectPoint src();
+
+ /**
+ * Returns the link destination connection point.
+ *
+ * @return link destination connection point
+ */
+ ConnectPoint dst();
+
+ /**
+ * Returns the link type.
+ *
+ * @return link type
+ */
+ Type type();
+
+ /**
+ * Returns the link state.
+ *
+ * @return link state
+ */
+ State state();
+
+ /**
+ * Indicates if the link is to be considered durable.
+ *
+ * @return true if the link is durable
+ */
+ boolean isDurable();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/LinkKey.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/LinkKey.java
new file mode 100644
index 00000000..9cb66492
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/LinkKey.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+// TODO Consider renaming.
+// it's an identifier for a Link, but it's not ElementId, so not using LinkId.
+
+/**
+ * Immutable representation of a link identity.
+ */
+public final class LinkKey {
+
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+
+ /**
+ * Returns source connection point.
+ *
+ * @return source connection point
+ */
+ public ConnectPoint src() {
+ return src;
+ }
+
+ /**
+ * Returns destination connection point.
+ *
+ * @return destination connection point
+ */
+ public ConnectPoint dst() {
+ return dst;
+ }
+
+ /**
+ * Creates a link identifier with source and destination connection point.
+ *
+ * @param src source connection point
+ * @param dst destination connection point
+ */
+ private LinkKey(ConnectPoint src, ConnectPoint dst) {
+ this.src = checkNotNull(src);
+ this.dst = checkNotNull(dst);
+ }
+
+ /**
+ * Creates a link identifier with source and destination connection point.
+ *
+ * @param src source connection point
+ * @param dst destination connection point
+ * @return a link identifier
+ */
+ public static LinkKey linkKey(ConnectPoint src, ConnectPoint dst) {
+ return new LinkKey(src, dst);
+ }
+
+ /**
+ * Creates a link identifier for the specified link.
+ *
+ * @param link link descriptor
+ * @return a link identifier
+ */
+ public static LinkKey linkKey(Link link) {
+ return new LinkKey(link.src(), link.dst());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(src, dst);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof LinkKey) {
+ final LinkKey other = (LinkKey) obj;
+ return Objects.equals(this.src, other.src) &&
+ Objects.equals(this.dst, other.dst);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("src", src)
+ .add("dst", dst)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/MastershipRole.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/MastershipRole.java
new file mode 100644
index 00000000..78f65864
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/MastershipRole.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Representation of a relationship role of a controller instance to a device
+ * or a region of network environment.
+ */
+public enum MastershipRole {
+
+ /**
+ * Represents a relationship where the controller instance is the master
+ * to a device or a region of network environment.
+ */
+ MASTER,
+
+ /**
+ * Represents a relationship where the controller instance is the standby,
+ * i.e. potential master to a device or a region of network environment.
+ */
+ STANDBY,
+
+ /**
+ * Represents that the controller instance is not eligible to be the master
+ * to a device or a region of network environment.
+ */
+ NONE
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/MutableAnnotations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/MutableAnnotations.java
new file mode 100644
index 00000000..9b7f328e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/MutableAnnotations.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Represents an mutable set of simple key/value string annotations.
+ */
+public interface MutableAnnotations extends Annotations {
+
+ /**
+ * Returns the value of the specified annotation.
+ *
+ * @param key annotation key
+ * @param value annotation value
+ * @return self
+ */
+ MutableAnnotations set(String key, String value);
+
+ /**
+ * Clears the specified keys or the all keys if none were specified.
+ *
+ * @param keys keys to be cleared
+ * @return self
+ */
+ MutableAnnotations clear(String... keys);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/NetTools.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/NetTools.java
new file mode 100644
index 00000000..fc561884
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/NetTools.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Networking domain tools.
+ */
+public final class NetTools {
+
+ private NetTools() {
+ }
+
+ /**
+ * Converts DPIDs of the form xx:xx:xx:xx:xx:xx:xx to OpenFlow provider
+ * device URIs. The is helpful for converting DPIDs coming from configuration
+ * or REST to URIs that the core understands.
+ *
+ * @param dpid the DPID string to convert
+ * @return the URI string for this device
+ */
+ public static String dpidToUri(String dpid) {
+ return "of:" + dpid.replace(":", "");
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/NetworkResource.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/NetworkResource.java
new file mode 100644
index 00000000..502208a0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/NetworkResource.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.
+ */
+package org.onosproject.net;
+
+/**
+ * Representation of a network resource, e.g. a link, lambda, MPLS tag.
+ */
+public interface NetworkResource {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchPort.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchPort.java
new file mode 100644
index 00000000..eb956f2a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchPort.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;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of OCh port (Optical Channel).
+ * Also referred to as a line side port (L-port) or narrow band port.
+ * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)"
+ */
+public class OchPort extends DefaultPort {
+
+ private final OduSignalType signalType;
+ private final boolean isTunable;
+ private final OchSignal lambda;
+
+ /**
+ * Creates an OCh port in the specified network element.
+ *
+ * @param element parent network element
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param signalType ODU signal type
+ * @param isTunable tunable wavelength capability
+ * @param lambda OCh signal
+ * @param annotations optional key/value annotations
+ */
+ public OchPort(Element element, PortNumber number, boolean isEnabled, OduSignalType signalType,
+ boolean isTunable, OchSignal lambda, Annotations... annotations) {
+ super(element, number, isEnabled, Type.OCH, 0, annotations);
+ this.signalType = signalType;
+ this.isTunable = isTunable;
+ this.lambda = checkNotNull(lambda);
+ }
+
+ /**
+ * Returns ODU signal type.
+ *
+ * @return ODU signal type
+ */
+ public OduSignalType signalType() {
+ return signalType;
+ }
+
+ /**
+ * Returns true if port is wavelength tunable.
+ *
+ * @return tunable wavelength capability
+ */
+ public boolean isTunable() {
+ return isTunable;
+ }
+
+ /**
+ * Returns OCh signal.
+ *
+ * @return OCh signal
+ */
+ public OchSignal lambda() {
+ return lambda;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number(), isEnabled(), type(), signalType, isTunable, lambda, annotations());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OchPort) {
+ final OchPort other = (OchPort) obj;
+ return Objects.equals(this.element().id(), other.element().id()) &&
+ Objects.equals(this.number(), other.number()) &&
+ Objects.equals(this.isEnabled(), other.isEnabled()) &&
+ Objects.equals(this.signalType, other.signalType) &&
+ Objects.equals(this.isTunable, other.isTunable) &&
+ Objects.equals(this.lambda, other.lambda) &&
+ Objects.equals(this.annotations(), other.annotations());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("element", element().id())
+ .add("number", number())
+ .add("isEnabled", isEnabled())
+ .add("type", type())
+ .add("signalType", signalType)
+ .add("isTunable", isTunable)
+ .add("lambda", lambda)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignal.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignal.java
new file mode 100644
index 00000000..5a5af34a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignal.java
@@ -0,0 +1,176 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.util.Frequency;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of Lambda representing OCh (Optical Channel) Signal.
+ *
+ * <p>
+ * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)".
+ * </p>
+ */
+public class OchSignal implements Lambda {
+
+ public static final Frequency CENTER_FREQUENCY = Frequency.ofTHz(193.1);
+ public static final Frequency FLEX_GRID_SLOT = Frequency.ofGHz(12.5);
+ private static final GridType DEFAULT_OCH_GRIDTYPE = GridType.DWDM;
+ private static final ChannelSpacing DEFAULT_CHANNEL_SPACING = ChannelSpacing.CHL_50GHZ;
+
+
+ private final GridType gridType;
+ private final ChannelSpacing channelSpacing;
+ // Frequency = 193.1 THz + spacingMultiplier * channelSpacing
+ private final int spacingMultiplier;
+ // Slot width = slotGranularity * 12.5 GHz
+ private final int slotGranularity;
+
+ /**
+ * Creates an instance with the specified arguments.
+ * It it recommended to use {@link Lambda#ochSignal(GridType, ChannelSpacing, int, int)}
+ * unless you want to use the concrete type, OchSignal, directly.
+ *
+ * @param gridType grid type
+ * @param channelSpacing channel spacing
+ * @param spacingMultiplier channel spacing multiplier
+ * @param slotGranularity slot width granularity
+ */
+ public OchSignal(GridType gridType, ChannelSpacing channelSpacing,
+ int spacingMultiplier, int slotGranularity) {
+ this.gridType = checkNotNull(gridType);
+ this.channelSpacing = checkNotNull(channelSpacing);
+ // Negative values are permitted for spacingMultiplier
+ this.spacingMultiplier = spacingMultiplier;
+ checkArgument(slotGranularity > 0, "slotGranularity must be larger than 0, received %s", slotGranularity);
+ this.slotGranularity = slotGranularity;
+ }
+
+ /**
+ * Create OCh signal from channel number.
+ *
+ * @param channel channel number
+ * @param maxFrequency maximum frequency
+ * @param grid grid spacing frequency
+ */
+ public OchSignal(int channel, Frequency maxFrequency, Frequency grid) {
+ // Calculate center frequency
+ Frequency centerFrequency = maxFrequency.subtract(grid.multiply(channel - 1));
+
+ this.gridType = DEFAULT_OCH_GRIDTYPE;
+ this.channelSpacing = DEFAULT_CHANNEL_SPACING;
+ this.spacingMultiplier = (int) (centerFrequency.subtract(OchSignal.CENTER_FREQUENCY).asHz() / grid.asHz());
+ this.slotGranularity = (int) Math.round((double) grid.asHz() / ChannelSpacing.CHL_12P5GHZ.frequency().asHz());
+ }
+
+ public OchSignal(Frequency centerFrequency, ChannelSpacing channelSpacing, int slotGranularity) {
+ this.gridType = DEFAULT_OCH_GRIDTYPE;
+ this.channelSpacing = channelSpacing;
+ this.spacingMultiplier = (int) Math.round((double) centerFrequency.
+ subtract(OchSignal.CENTER_FREQUENCY).asHz() / channelSpacing().frequency().asHz());
+ this.slotGranularity = slotGranularity;
+ }
+
+ /**
+ * Returns grid type.
+ *
+ * @return grid type
+ */
+ public GridType gridType() {
+ return gridType;
+ }
+
+ /**
+ * Returns channel spacing.
+ *
+ * @return channel spacing
+ */
+ public ChannelSpacing channelSpacing() {
+ return channelSpacing;
+ }
+
+ /**
+ * Returns spacing multiplier.
+ *
+ * @return spacing multiplier
+ */
+ public int spacingMultiplier() {
+ return spacingMultiplier;
+ }
+
+ /**
+ * Returns slow width granularity.
+ *
+ * @return slow width granularity
+ */
+ public int slotGranularity() {
+ return slotGranularity;
+ }
+
+ /**
+ * Returns central frequency in MHz.
+ *
+ * @return frequency in MHz
+ */
+ public Frequency centralFrequency() {
+ return CENTER_FREQUENCY.add(channelSpacing().frequency().multiply(spacingMultiplier));
+ }
+
+ /**
+ * Returns slot width.
+ *
+ * @return slot width
+ */
+ public Frequency slotWidth() {
+ return FLEX_GRID_SLOT.multiply(slotGranularity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(gridType, channelSpacing, spacingMultiplier, slotGranularity);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof OchSignal)) {
+ return false;
+ }
+ final OchSignal other = (OchSignal) obj;
+ return Objects.equals(this.gridType, other.gridType)
+ && Objects.equals(this.channelSpacing, other.channelSpacing)
+ && Objects.equals(this.spacingMultiplier, other.spacingMultiplier)
+ && Objects.equals(this.slotGranularity, other.slotGranularity);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("gridType", gridType)
+ .add("channelSpacing", channelSpacing)
+ .add("spacingMultiplier", spacingMultiplier)
+ .add("slotGranularity", slotGranularity)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignalType.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignalType.java
new file mode 100644
index 00000000..9caf3f34
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OchSignalType.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Represents OCh (Optical Channel) signal type defined in
+ * Open Networking Foundation "Optical Transport Protocol Extensions Version 1.0".
+ */
+public enum OchSignalType {
+ /**
+ * Represents fixed grid.
+ */
+ FIXED_GRID,
+
+ /**
+ * Represents flex grid.
+ */
+ FLEX_GRID
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/OduCltPort.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OduCltPort.java
new file mode 100644
index 00000000..e5602f36
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OduCltPort.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of ODU client port (Optical channel Data Unit).
+ * Also referred to as a T-port or wide band port.
+ * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)"
+ */
+
+public class OduCltPort extends DefaultPort {
+
+ public enum SignalType {
+ CLT_1GBE,
+ CLT_10GBE,
+ CLT_40GBE,
+ CLT_100GBE
+ }
+
+ private final SignalType signalType;
+
+
+ /**
+ * Creates an ODU client port in the specified network element.
+ *
+ * @param element parent network element
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param signalType ODU client signal type
+ * @param annotations optional key/value annotations
+ */
+ public OduCltPort(Element element, PortNumber number, boolean isEnabled,
+ SignalType signalType, Annotations... annotations) {
+ super(element, number, isEnabled, Type.ODUCLT, 0, annotations);
+ this.signalType = signalType;
+ }
+
+ /**
+ * Returns ODU client signal type.
+ *
+ * @return ODU client signal type
+ */
+ public SignalType signalType() {
+ return signalType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number(), isEnabled(), type(), signalType, annotations());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OduCltPort) {
+ final OduCltPort other = (OduCltPort) obj;
+ return Objects.equals(this.element().id(), other.element().id()) &&
+ Objects.equals(this.number(), other.number()) &&
+ Objects.equals(this.isEnabled(), other.isEnabled()) &&
+ Objects.equals(this.signalType, other.signalType) &&
+ Objects.equals(this.annotations(), other.annotations());
+ }
+ return false;
+ }
+
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("element", element().id())
+ .add("number", number())
+ .add("isEnabled", isEnabled())
+ .add("type", type())
+ .add("signalType", signalType)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/OduSignalType.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OduSignalType.java
new file mode 100644
index 00000000..014c893b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OduSignalType.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Represents ODU (Optical channel Data Unit) signal type.
+ *
+ * <p>
+ * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)" and
+ * Open Networking Foundation "Optical Transport Protocol Extensions Version 1.0".
+ * </p>
+ */
+public enum OduSignalType {
+ ODU0,
+ ODU1,
+ ODU2,
+ ODU2e,
+ ODU3,
+ ODU4
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/OmsPort.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OmsPort.java
new file mode 100644
index 00000000..753834b5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/OmsPort.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import org.onlab.util.Frequency;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of OMS port (Optical Multiplexing Section).
+ * Also referred to as a WDM port or W-port.
+ * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)"
+ *
+ * Assumes we only support fixed grid for now.
+ */
+public class OmsPort extends DefaultPort {
+
+ private final Frequency minFrequency; // Minimum frequency
+ private final Frequency maxFrequency; // Maximum frequency
+ private final Frequency grid; // Grid spacing frequency
+
+
+ /**
+ * Creates an OMS port in the specified network element.
+ *
+ * @param element parent network element
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param minFrequency minimum frequency
+ * @param maxFrequency maximum frequency
+ * @param grid grid spacing frequency
+ * @param annotations optional key/value annotations
+ */
+ public OmsPort(Element element, PortNumber number, boolean isEnabled,
+ Frequency minFrequency, Frequency maxFrequency, Frequency grid, Annotations... annotations) {
+ super(element, number, isEnabled, Type.OMS, 0, annotations);
+ this.minFrequency = minFrequency;
+ this.maxFrequency = maxFrequency;
+ this.grid = grid;
+ }
+
+ /**
+ * Returns the total number of channels on the port.
+ *
+ * @return total number of channels
+ */
+ public short totalChannels() {
+ Frequency diff = maxFrequency.subtract(minFrequency);
+ return (short) (diff.asHz() / (grid.asHz() + 1));
+ }
+
+ /**
+ * Returns the minimum frequency.
+ *
+ * @return minimum frequency
+ */
+ public Frequency minFrequency() {
+ return minFrequency;
+ }
+
+ /**
+ * Returns the maximum frequency.
+ *
+ * @return maximum frequency
+ */
+ public Frequency maxFrequency() {
+ return maxFrequency;
+ }
+
+ /**
+ * Returns the grid spacing frequency.
+ *
+ * @return grid spacing frequency
+ */
+ public Frequency grid() {
+ return grid;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number(), isEnabled(), type(),
+ minFrequency, maxFrequency, grid, annotations());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OmsPort) {
+ final OmsPort other = (OmsPort) obj;
+ return Objects.equals(this.element().id(), other.element().id()) &&
+ Objects.equals(this.number(), other.number()) &&
+ Objects.equals(this.isEnabled(), other.isEnabled()) &&
+ Objects.equals(this.minFrequency, other.minFrequency) &&
+ Objects.equals(this.maxFrequency, other.maxFrequency) &&
+ Objects.equals(this.grid, other.grid) &&
+ Objects.equals(this.annotations(), other.annotations());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("element", element().id())
+ .add("number", number())
+ .add("isEnabled", isEnabled())
+ .add("type", type())
+ .add("minFrequency", minFrequency)
+ .add("maxFrequency", maxFrequency)
+ .add("grid", grid)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Path.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Path.java
new file mode 100644
index 00000000..c9c20c4c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Path.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * Representation of a contiguous directed path in a network. Path comprises
+ * of a sequence of links, where adjacent links must share the same device,
+ * meaning that destination of the source of one link must coincide with the
+ * destination of the previous link.
+ */
+public interface Path extends Link {
+
+ /**
+ * Returns sequence of links comprising the path.
+ *
+ * @return list of links
+ */
+ List<Link> links();
+
+ /**
+ * Returns the path cost as a unit-less value.
+ *
+ * @return unit-less path cost
+ */
+ double cost();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Port.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Port.java
new file mode 100644
index 00000000..d70b1e17
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Port.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+
+/**
+ * Abstraction of a network port.
+ */
+public interface Port extends Annotated {
+
+ /** Represents coarse port type classification. */
+ enum Type {
+ /**
+ * Signifies copper-based connectivity.
+ */
+ COPPER,
+
+ /**
+ * Signifies optical fiber-based connectivity.
+ */
+ FIBER,
+
+ /**
+ * Signifies optical fiber-based packet port.
+ */
+ PACKET,
+
+ /**
+ * Signifies optical fiber-based optical tributary port (called T-port).
+ * The signal from the client side will be formed into a ITU G.709 (OTN) frame.
+ */
+ ODUCLT,
+
+ /**
+ * Signifies optical fiber-based Line-side port (called L-port).
+ */
+ OCH,
+
+ /**
+ * Signifies optical fiber-based WDM port (called W-port).
+ * Optical Multiplexing Section (See ITU G.709).
+ */
+ OMS,
+
+ /**
+ * Signifies virtual port.
+ */
+ VIRTUAL
+ }
+
+ /**
+ * Returns the parent network element to which this port belongs.
+ *
+ * @return parent network element
+ */
+ Element element();
+
+ /**
+ * Returns the port number.
+ *
+ * @return port number
+ */
+ PortNumber number();
+
+ /**
+ * Indicates whether or not the port is currently up and active.
+ *
+ * @return true if the port is operational
+ */
+ boolean isEnabled();
+
+ /**
+ * Returns the port type.
+ *
+ * @return port type
+ */
+ Type type();
+
+ /**
+ * Returns the current port speed in Mbps.
+ *
+ * @return current port speed
+ */
+ long portSpeed();
+
+ // TODO: more attributes?
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/PortNumber.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/PortNumber.java
new file mode 100644
index 00000000..03e6dba9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/PortNumber.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+
+import com.google.common.primitives.UnsignedLongs;
+
+/**
+ * Representation of a port number.
+ */
+public final class PortNumber {
+
+ public static final PortNumber P0 = portNumber(0);
+
+ // TODO: revisit the max and the logical port value assignments
+
+ private static final long MAX_NUMBER = (2L * Integer.MAX_VALUE) + 1;
+
+
+ static final long IN_PORT_NUMBER = -8L;
+ static final long TABLE_NUMBER = -7L;
+ static final long NORMAL_NUMBER = -6L;
+ static final long FLOOD_NUMBER = -5L;
+ static final long ALL_NUMBER = -4L;
+ static final long LOCAL_NUMBER = -2L;
+ static final long CONTROLLER_NUMBER = -3L;
+
+ public static final PortNumber IN_PORT = new PortNumber(IN_PORT_NUMBER);
+ public static final PortNumber TABLE = new PortNumber(TABLE_NUMBER);
+ public static final PortNumber NORMAL = new PortNumber(NORMAL_NUMBER);
+ public static final PortNumber FLOOD = new PortNumber(FLOOD_NUMBER);
+ public static final PortNumber ALL = new PortNumber(ALL_NUMBER);
+ public static final PortNumber LOCAL = new PortNumber(LOCAL_NUMBER);
+ public static final PortNumber CONTROLLER = new PortNumber(CONTROLLER_NUMBER);
+
+ private final long number;
+ private final String name;
+ private final boolean hasName;
+
+ // Public creation is prohibited
+ private PortNumber(long number) {
+ this.number = number;
+ this.name = UnsignedLongs.toString(number);
+ this.hasName = false;
+ }
+
+ private PortNumber(long number, String name) {
+ this.number = number;
+ this.name = name;
+ this.hasName = true;
+ }
+
+ /**
+ * Returns the port number representing the specified long value.
+ *
+ * @param number port number as long value
+ * @return port number
+ */
+ public static PortNumber portNumber(long number) {
+ return new PortNumber(number);
+ }
+
+ /**
+ * Returns the port number representing the specified string value.
+ *
+ * @param string port number as string value
+ * @return port number
+ */
+ public static PortNumber portNumber(String string) {
+ return new PortNumber(UnsignedLongs.decode(string));
+ }
+
+ /**
+ * Returns the port number representing the specified long value and name.
+ *
+ * @param number port number as long value
+ * @param name port name as string value
+ * @return port number
+ */
+ public static PortNumber portNumber(long number, String name) {
+ return new PortNumber(number, name);
+ }
+
+ /**
+ * Indicates whether or not this port number is a reserved logical one or
+ * whether it corresponds to a normal physical port of a device or NIC.
+ *
+ * @return true if logical port number
+ */
+ public boolean isLogical() {
+ if (hasName) {
+ return false;
+ } else {
+ return (number < 0 || number > MAX_NUMBER);
+ }
+ }
+
+ /**
+ * Returns the backing long value.
+ *
+ * @return port number as long
+ */
+ public long toLong() {
+ return number;
+ }
+
+ /**
+ * Returns the backing string value.
+ *
+ * @return port name as string value
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Indicates whether this port number was created with a port name,
+ * or only with a number.
+ *
+ * @return true if port was created with name
+ */
+ public boolean hasName() {
+ return hasName;
+ }
+
+ private String decodeLogicalPort() {
+ if (number == CONTROLLER_NUMBER) {
+ return "CONTROLLER";
+ } else if (number == LOCAL_NUMBER) {
+ return "LOCAL";
+ } else if (number == ALL_NUMBER) {
+ return "ALL";
+ } else if (number == FLOOD_NUMBER) {
+ return "FLOOD";
+ } else if (number == NORMAL_NUMBER) {
+ return "NORMAL";
+ } else if (number == TABLE_NUMBER) {
+ return "TABLE";
+ } else if (number == IN_PORT_NUMBER) {
+ return "IN_PORT";
+ }
+ return "UNKNOWN";
+ }
+
+ @Override
+ public String toString() {
+ if (!isLogical()) {
+ return name;
+ } else {
+ return decodeLogicalPort();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(number);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PortNumber) {
+ final PortNumber other = (PortNumber) obj;
+ return this.number == other.number;
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/Provided.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Provided.java
new file mode 100644
index 00000000..daf85e75
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/Provided.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Abstraction of an entity supplied by a provider.
+ */
+public interface Provided {
+
+ /**
+ * Returns the identifier of the provider which supplied the entity.
+ *
+ * @return provider identification
+ */
+ ProviderId providerId();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/SparseAnnotations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/SparseAnnotations.java
new file mode 100644
index 00000000..97961b3a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/SparseAnnotations.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+/**
+ * Represents an set of sparse key/value string annotations capable of carrying
+ * annotation keys tagged for removal.
+ */
+public interface SparseAnnotations extends Annotations {
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Note that this set includes keys for any attributes tagged for removal.
+ * </p>
+ */
+ @Override
+ Set<String> keys();
+
+ /**
+ * Indicates whether the specified key has been tagged as removed. This is
+ * used for merging sparse annotation sets.
+ *
+ * @param key annotation key
+ * @return true if the previous annotation has been tagged for removal
+ */
+ boolean isRemoved(String key);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeConfig.java
new file mode 100644
index 00000000..7f157e95
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeConfig.java
@@ -0,0 +1,87 @@
+/*
+ * 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.behaviour;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.driver.HandlerBehaviour;
+
+/**
+ * Behaviour for handling various drivers for bridge configurations.
+ */
+public interface BridgeConfig extends HandlerBehaviour {
+
+ /**
+ * Add a bridge.
+ *
+ * @param bridgeName bridge name
+ */
+ void addBridge(BridgeName bridgeName);
+
+ /**
+ * Remove a bridge.
+ *
+ * @param bridgeName bridge name
+ */
+ void deleteBridge(BridgeName bridgeName);
+
+ /**
+ * Remove a bridge.
+ *
+ * @return bridge collection
+ */
+ Collection<BridgeDescription> getBridges();
+
+ /**
+ * Add a logical/virtual port.
+ *
+ * @param port port number
+ */
+ void addPort(PortDescription port);
+
+ /**
+ * Delete a logical/virtual port.
+ *
+ * @param port port number
+ */
+ void deletePort(PortDescription port);
+
+ /**
+ * Delete a logical/virtual port.
+ *
+ * @return collection of port
+ */
+ Collection<PortDescription> getPorts();
+
+ /**
+ * Get a collection of port.
+ *
+ * @return portNumbers set of PortNumber
+ */
+ Set<PortNumber> getPortNumbers();
+
+ /**
+ * Get logical/virtual ports by ifaceIds.
+ *
+ * @param ifaceIds the ifaceid that needed
+ * @return list of PortNumber
+ */
+ List<PortNumber> getLocalPorts(Iterable<String> ifaceIds);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeDescription.java
new file mode 100644
index 00000000..3c1d5542
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeDescription.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.behaviour;
+
+import org.onosproject.net.Description;
+import org.onosproject.net.DeviceId;
+
+/**
+ * The abstraction of bridge in OVSDB protocol.
+ */
+public interface BridgeDescription extends Description {
+
+ /**
+ * Returns bridge name.
+ *
+ * @return bridge name
+ */
+ BridgeName bridgeName();
+
+ /**
+ * Returns controller identifier that this bridge belongs to.
+ *
+ * @return controller identifier
+ */
+ DeviceId cotrollerDeviceId();
+
+ /**
+ * Returns bridge identifier .
+ *
+ * @return bridge identifier
+ */
+ DeviceId deviceId();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeName.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeName.java
new file mode 100644
index 00000000..3f782954
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/BridgeName.java
@@ -0,0 +1,78 @@
+/*
+ * 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.behaviour;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents for a bridge name.
+ */
+public final class BridgeName {
+
+ private final String name;
+
+ // Public construction is prohibited
+ private BridgeName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Creates a bridge name using the supplied string.
+ *
+ * @param name bridge name
+ * @return BridgeName
+ */
+ public static BridgeName bridgeName(String name) {
+ return new BridgeName(name);
+ }
+
+ /**
+ * Returns the bridge name string.
+ *
+ * @return name string
+ */
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof BridgeName) {
+ final BridgeName that = (BridgeName) obj;
+ return this.getClass() == that.getClass() &&
+ Objects.equals(this.name, that.name);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerConfig.java
new file mode 100644
index 00000000..bb8a788b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerConfig.java
@@ -0,0 +1,39 @@
+/*
+ * 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.behaviour;
+
+import java.util.List;
+
+/**
+ * Device behaviour to obtain and set controllers at the device.
+ */
+public interface ControllerConfig {
+
+ //TODO: add other controller parameters as needed.
+
+ /**
+ * Obtain the list of controller which are currently configured.
+ * @return a list for controller descriptions
+ */
+ List<ControllerInfo> getControllers();
+
+ /**
+ * Set a list of controllers on a device.
+ * @param controllers a list of controller descriptions
+ */
+ void setControllers(List<ControllerInfo> controllers);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java
new file mode 100644
index 00000000..9ff808a9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/ControllerInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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.behaviour;
+
+import org.onlab.packet.IpAddress;
+
+/**
+ * Represents information for a device to connect to a controller.
+ */
+public class ControllerInfo {
+
+ public final IpAddress ip;
+ public final int tcpPort;
+
+ /**
+ * Information for contacting the controller.
+ *
+ * @param ip the ip address
+ * @param tcpPort the tcp port
+ */
+ public ControllerInfo(IpAddress ip, int tcpPort) {
+ this.ip = ip;
+ this.tcpPort = tcpPort;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultBridgeDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultBridgeDescription.java
new file mode 100644
index 00000000..6a6f670f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultBridgeDescription.java
@@ -0,0 +1,87 @@
+/*
+ * 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.behaviour;
+
+import java.util.Objects;
+
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.SparseAnnotations;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * The default implementation of bridge.
+ */
+public final class DefaultBridgeDescription extends AbstractDescription
+ implements BridgeDescription {
+
+ private final BridgeName name;
+ private final DeviceId deviceId;
+ private final DeviceId controllerId;
+
+ public DefaultBridgeDescription(BridgeName name, DeviceId controllerId,
+ DeviceId deviceId,
+ SparseAnnotations... annotations) {
+ super(annotations);
+ this.name = name;
+ this.deviceId = deviceId;
+ this.controllerId = controllerId;
+ }
+
+ @Override
+ public BridgeName bridgeName() {
+ return name;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public DeviceId cotrollerDeviceId() {
+ return controllerId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, deviceId, controllerId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultBridgeDescription) {
+ final DefaultBridgeDescription that = (DefaultBridgeDescription) obj;
+ return this.getClass() == that.getClass()
+ && Objects.equals(this.name, that.name)
+ && Objects.equals(this.deviceId, that.deviceId)
+ && Objects.equals(this.controllerId, that.controllerId);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("name", name)
+ .add("deviceId", deviceId).add("controllerId", controllerId)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultNextGroup.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultNextGroup.java
new file mode 100644
index 00000000..ef1f9de7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultNextGroup.java
@@ -0,0 +1,33 @@
+/*
+ * 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.behaviour;
+
+/**
+ * Default implementation of a next group.
+ */
+public class DefaultNextGroup implements NextGroup {
+
+ private final byte[] data;
+
+ public DefaultNextGroup(byte[] data) {
+ this.data = data;
+ }
+
+ @Override
+ public byte[] data() {
+ return data;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultTunnelDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultTunnelDescription.java
new file mode 100644
index 00000000..7554a3cb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/DefaultTunnelDescription.java
@@ -0,0 +1,87 @@
+/*
+ * 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.behaviour;
+
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.SparseAnnotations;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+
+/**
+ * Default implementation of immutable tunnel description entity.
+ */
+@Beta
+public class DefaultTunnelDescription extends AbstractDescription
+ implements TunnelDescription {
+
+ private final TunnelEndPoint src;
+ private final TunnelEndPoint dst;
+ private final Type type;
+ // which a tunnel match up
+ // tunnel producer
+ private final TunnelName tunnelName; // name of a tunnel
+
+ /**
+ * Creates a tunnel description using the supplied information.
+ *
+ * @param src TunnelPoint source
+ * @param dst TunnelPoint destination
+ * @param type tunnel type
+ * @param tunnelName tunnel name
+ * @param annotations optional key/value annotations
+ */
+ public DefaultTunnelDescription(TunnelEndPoint src,
+ TunnelEndPoint dst, Type type,
+ TunnelName tunnelName,
+ SparseAnnotations... annotations) {
+ super(annotations);
+ this.src = src;
+ this.dst = dst;
+ this.type = type;
+ this.tunnelName = tunnelName;
+ }
+
+ @Override
+ public TunnelEndPoint src() {
+ return src;
+ }
+
+ @Override
+ public TunnelEndPoint dst() {
+ return dst;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public TunnelName tunnelName() {
+ return tunnelName;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("src", src())
+ .add("dst", dst())
+ .add("type", type())
+ .add("tunnelName", tunnelName())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/IpTunnelEndPoint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/IpTunnelEndPoint.java
new file mode 100644
index 00000000..83ad4756
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/IpTunnelEndPoint.java
@@ -0,0 +1,80 @@
+/*
+ * 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.behaviour;
+
+import java.util.Objects;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.IpAddress;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represent for a tunnel point using ip address.
+ */
+@Beta
+public final class IpTunnelEndPoint implements TunnelEndPoint {
+
+ private final IpAddress ip;
+
+ /**
+ * Public construction is prohibited.
+ * @param ip ip address
+ */
+ private IpTunnelEndPoint(IpAddress ip) {
+ this.ip = ip;
+ }
+
+ /**
+ * Create a IP tunnel end point.
+ * @param ip IP address
+ * @return IpTunnelEndPoint
+ */
+ public static IpTunnelEndPoint ipTunnelPoint(IpAddress ip) {
+ return new IpTunnelEndPoint(ip);
+ }
+
+ /**
+ * Returns IP address.
+ * @return IP address
+ */
+ public IpAddress ip() {
+ return ip;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ip);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IpTunnelEndPoint) {
+ final IpTunnelEndPoint other = (IpTunnelEndPoint) obj;
+ return Objects.equals(this.ip, other.ip);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("ip", ip).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/MplsQuery.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/MplsQuery.java
new file mode 100644
index 00000000..0e9f466d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/MplsQuery.java
@@ -0,0 +1,35 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.HandlerBehaviour;
+
+/**
+ * A HandlerBehaviour to check the capability of MPLS.
+ */
+@Beta
+public interface MplsQuery extends HandlerBehaviour {
+
+ /**
+ * Indicates if MPLS can be used at the port.
+
+ * @param port port to be checked for the capability
+ * @return true if MPLS can be used at the port, false otherwise.
+ */
+ boolean isEnabled(PortNumber port);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/NextGroup.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/NextGroup.java
new file mode 100644
index 00000000..b5a3891c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/NextGroup.java
@@ -0,0 +1,30 @@
+/*
+ * 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.behaviour;
+
+/**
+ * Opaque data type for carrying group-like information.
+ * Only relevant to a pipeliner driver.
+ */
+public interface NextGroup {
+
+ /**
+ * Serialized form of the next group.
+ * @return a byte array.
+ */
+ byte[] data();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/Pipeliner.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/Pipeliner.java
new file mode 100644
index 00000000..dcfc5883
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/Pipeliner.java
@@ -0,0 +1,57 @@
+/*
+ * 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.behaviour;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.HandlerBehaviour;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+
+/**
+ * Behaviour for handling various pipelines.
+ */
+public interface Pipeliner extends HandlerBehaviour {
+
+ /**
+ * Initializes the driver with context required for its operation.
+ *
+ * @param deviceId the deviceId
+ * @param context processing context
+ */
+ void init(DeviceId deviceId, PipelinerContext context);
+
+ /**
+ * Installs the filtering rules onto the device.
+ *
+ * @param filterObjective a filtering objective
+ */
+ void filter(FilteringObjective filterObjective);
+
+ /**
+ * Installs the forwarding rules onto the device.
+ *
+ * @param forwardObjective a forwarding objective
+ */
+ void forward(ForwardingObjective forwardObjective);
+
+ /**
+ * Installs the next hop elements into the device.
+ *
+ * @param nextObjective a next objectives
+ */
+ void next(NextObjective nextObjective);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PipelinerContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PipelinerContext.java
new file mode 100644
index 00000000..d0ca42b1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PipelinerContext.java
@@ -0,0 +1,41 @@
+/*
+ * 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.behaviour;
+
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+
+/**
+ * Processing context and supporting services for the pipeline behaviour.
+ */
+public interface PipelinerContext {
+
+ /**
+ * Returns the service directory which can be used to obtain references
+ * to various supporting services.
+ *
+ * @return service directory
+ */
+ ServiceDirectory directory();
+
+ /**
+ * Returns the Objective Store where data can be stored and retrieved.
+ * @return the flow objective store
+ */
+ FlowObjectiveStore store();
+
+ // TODO: add means to store and access shared state
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortAdmin.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortAdmin.java
new file mode 100644
index 00000000..141e27dc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortAdmin.java
@@ -0,0 +1,33 @@
+/*
+ * 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.behaviour;
+
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.driver.HandlerBehaviour;
+
+/**
+ * Means to administratively enable/disable a logical port at the device.
+ */
+public interface PortAdmin extends HandlerBehaviour {
+
+ /**
+ * Enable/disable administratively a port.
+ *
+ * @param port a port description containing the desired port state
+ */
+ void enable(PortDescription port);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortConfig.java
new file mode 100644
index 00000000..83dd99d8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/PortConfig.java
@@ -0,0 +1,40 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.primitives.UnsignedInteger;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.driver.HandlerBehaviour;
+
+/**
+ * Means to configure a logical port at the device.
+ */
+public interface PortConfig extends HandlerBehaviour {
+
+ /**
+ * Apply QoS configuration on a device.
+ * @param port a port description
+ * @param queueId an unsigned integer
+ */
+ void applyQoS(PortDescription port, UnsignedInteger queueId);
+
+ /**
+ * Remove a QoS configuration.
+ * @param port a port description
+ */
+ void removeQoS(PortDescription port);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueConfig.java
new file mode 100644
index 00000000..22f3ecb7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueConfig.java
@@ -0,0 +1,56 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.primitives.UnsignedInteger;
+
+import java.util.Set;
+
+/**
+ * Means to alter a device's dataplane queues.
+ */
+public interface QueueConfig {
+
+ /**
+ * Obtain all queues configured on a device.
+ *
+ * @return a list of queue descriptions
+ */
+ Set<QueueInfo> getQueues();
+
+ /**
+ * Obtain a specific queue given a queue id.
+ *
+ * @param queueId an unsigned integer representing a queue id
+ * @return a queue description
+ */
+ QueueInfo getQueue(UnsignedInteger queueId);
+
+ /**
+ * Add a queue to a device.
+ *
+ * @param queue a queue description
+ */
+ void addQueue(QueueInfo queue);
+
+ /**
+ * Remove a queue from a device.
+ *
+ * @param queueId an unsigned integer
+ */
+ void removeQueue(UnsignedInteger queueId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueInfo.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueInfo.java
new file mode 100644
index 00000000..25852b36
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/QueueInfo.java
@@ -0,0 +1,56 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.primitives.UnsignedInteger;
+
+/**
+ * Represents a dataplane queue.
+ */
+public class QueueInfo {
+
+ public enum Type {
+ /**
+ * Supports burst and priority as well as min and max rates.
+ */
+ FULL,
+
+ /**
+ * Only support min and max rates.
+ */
+ MINMAX
+ }
+
+ private final UnsignedInteger queueId;
+ private final Type type;
+ private final long minRate;
+ private final long maxRate;
+ private final long burst;
+ private final long priority;
+
+ public QueueInfo(UnsignedInteger queueId, Type type, long minRate,
+ long maxRate, long burst, long priority) {
+ this.queueId = queueId;
+ this.type = type;
+ this.minRate = minRate;
+ this.maxRate = maxRate;
+ this.burst = burst;
+ this.priority = priority;
+ }
+
+ //TODO builder
+ // public static QueueInfoBuilder builder() {}
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelConfig.java
new file mode 100644
index 00000000..7e79a57e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelConfig.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.behaviour;
+
+import java.util.Collection;
+
+import org.onosproject.net.driver.HandlerBehaviour;
+
+/**
+ * Behaviour for handling various drivers for tunnel configuration.
+ */
+public interface TunnelConfig extends HandlerBehaviour {
+
+ /**
+ * Creates a tunnel on this device.
+ *
+ * @param tunnel tunnel descriptor
+ */
+ void createTunnel(TunnelDescription tunnel);
+
+ /**
+ * Removes a tunnel on this device.
+ *
+ * @param tunnel tunnel descriptor
+ */
+ void removeTunnel(TunnelDescription tunnel);
+
+ /**
+ * Updates a tunnel on this device.
+ *
+ * @param tunnel tunnel descriptor
+ */
+ void updateTunnel(TunnelDescription tunnel);
+
+ /**
+ * Returns tunnels created on this device.
+ *
+ * @return collection of tunnels
+ */
+ Collection<TunnelDescription> getTunnels();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelDescription.java
new file mode 100755
index 00000000..b2fb6996
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelDescription.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.behaviour;
+
+import org.onosproject.net.Annotated;
+import org.onosproject.net.Description;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Describes a tunnel.
+ */
+@Beta
+public interface TunnelDescription extends Description, Annotated {
+
+ /**
+ * Tunnel technology type.
+ */
+ enum Type {
+ /**
+ * Signifies that this is a MPLS tunnel.
+ */
+ MPLS,
+ /**
+ * Signifies that this is a L2 tunnel.
+ */
+ VLAN,
+ /**
+ * Signifies that this is a DC L2 extension tunnel.
+ */
+ VXLAN,
+ /**
+ * Signifies that this is a L3 tunnel.
+ */
+ GRE,
+ /**
+ * Signifies that this is a L1 OTN tunnel.
+ */
+ ODUK,
+ /**
+ * Signifies that this is a L0 OCH tunnel.
+ */
+ OCH
+ }
+
+ /**
+ * Returns the connection point source.
+ *
+ * @return tunnel source ConnectionPoint
+ */
+ TunnelEndPoint src();
+
+ /**
+ * Returns the connection point destination.
+ *
+ * @return tunnel destination
+ */
+ TunnelEndPoint dst();
+
+ /**
+ * Returns the tunnel type.
+ *
+ * @return tunnel type
+ */
+ Type type();
+
+ /**
+ * Return the name of a tunnel.
+ *
+ * @return Tunnel Name
+ */
+ TunnelName tunnelName();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java
new file mode 100644
index 00000000..c354c38d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java
@@ -0,0 +1,28 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Represents for source end point or destination end point of a tunnel. Maybe a tunnel
+ * based on ConnectPoint, IpAddress, MacAddress and so on is built.
+ */
+@Beta
+public interface TunnelEndPoint {
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelName.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelName.java
new file mode 100644
index 00000000..9be26549
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/TunnelName.java
@@ -0,0 +1,79 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Objects;
+
+/**
+ * Represents for a unique tunnel name. TunnelId is generated by ONOS while
+ * TunnelName is given by producer. The consumer can borrow tunnels with
+ * TunnelId or TunnelName.
+ */
+@Beta
+public final class TunnelName {
+ private final String str;
+
+ // Default constructor for serialization
+ private TunnelName(String tunnelName) {
+ this.str = tunnelName;
+ }
+
+
+ /**
+ * Creates a tunnel name using the supplied URI string.
+ *
+ * @param tunnelName tunnel name string
+ * @return tunnel name object
+ */
+ public static TunnelName tunnelName(String tunnelName) {
+ return new TunnelName(tunnelName);
+ }
+
+ /**
+ * The string of tunnel name.
+ *
+ * @return the string of tunnel name
+ */
+ public String value() {
+ return str;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(str);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof TunnelName) {
+ final TunnelName that = (TunnelName) obj;
+ return this.getClass() == that.getClass()
+ && Objects.equals(this.str, that.str);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return str;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/VlanQuery.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/VlanQuery.java
new file mode 100644
index 00000000..a1057c90
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/VlanQuery.java
@@ -0,0 +1,35 @@
+/*
+ * 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.behaviour;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.HandlerBehaviour;
+
+/**
+ * A HandlerBehaviour to check the capability of VLAN.
+ */
+@Beta
+public interface VlanQuery extends HandlerBehaviour {
+
+ /**
+ * Indicates if VLAN can be used at the port.
+ *
+ * @param port port to be checked for the capability
+ * @return true if VLAN can be used at the port, false otherwise.
+ */
+ boolean isEnabled(PortNumber port);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/package-info.java
new file mode 100644
index 00000000..f0a9a5e5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/behaviour/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Abstractions of various device configuration or device adaptation behaviours;
+ * counterpart to the device driver subsystem.
+ */
+package org.onosproject.net.behaviour; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/Config.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/Config.java
new file mode 100644
index 00000000..5cdc0c12
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/Config.java
@@ -0,0 +1,312 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Lists;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base abstraction of a configuration facade for a specific subject. Derived
+ * classes should keep all state in the specified JSON tree as that is the
+ * only state that will be distributed or persisted; this class is merely
+ * a facade for interacting with a particular facet of configuration on a
+ * given subject.
+ *
+ * @param <S> type of subject
+ */
+@Beta
+public abstract class Config<S> {
+
+ protected S subject;
+ protected String key;
+
+ protected JsonNode node;
+ protected ObjectNode object;
+ protected ArrayNode array;
+ protected ObjectMapper mapper;
+
+ protected ConfigApplyDelegate delegate;
+
+ /**
+ * Initializes the configuration behaviour with necessary context.
+ *
+ * @param subject configuration subject
+ * @param key configuration key
+ * @param node JSON node where configuration data is stored
+ * @param mapper JSON object mapper
+ * @param delegate delegate context
+ */
+ public void init(S subject, String key, JsonNode node, ObjectMapper mapper,
+ ConfigApplyDelegate delegate) {
+ this.subject = checkNotNull(subject);
+ this.key = key;
+ this.node = checkNotNull(node);
+ this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
+ this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
+ this.mapper = checkNotNull(mapper);
+ this.delegate = checkNotNull(delegate);
+ }
+
+ /**
+ * Returns the specific subject to which this configuration pertains.
+ *
+ * @return configuration subject
+ */
+ public S subject() {
+ return subject;
+ }
+
+ /**
+ * Returns the configuration key. This is primarily aimed for use in
+ * composite JSON trees in external representations and has no bearing on
+ * the internal behaviours.
+ *
+ * @return configuration key
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns the JSON node that contains the configuration data.
+ *
+ * @return JSON node backing the configuration
+ */
+ public JsonNode node() {
+ return node;
+ }
+
+ /**
+ * Applies any configuration changes made via this configuration.
+ */
+ public void apply() {
+ delegate.onApply(this);
+ }
+
+
+ // Miscellaneous helpers for interacting with JSON
+
+ /**
+ * Gets the specified property as a string.
+ *
+ * @param name property name
+ * @param defaultValue default value if property not set
+ * @return property value or default value
+ */
+ protected String get(String name, String defaultValue) {
+ return object.path(name).asText(defaultValue);
+ }
+
+ /**
+ * Sets the specified property as a string or clears it if null value given.
+ *
+ * @param name property name
+ * @param value new value or null to clear the property
+ * @return self
+ */
+ protected Config<S> setOrClear(String name, String value) {
+ if (value != null) {
+ object.put(name, value);
+ } else {
+ object.remove(name);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the specified property as a boolean.
+ *
+ * @param name property name
+ * @param defaultValue default value if property not set
+ * @return property value or default value
+ */
+ protected boolean get(String name, boolean defaultValue) {
+ return object.path(name).asBoolean(defaultValue);
+ }
+
+ /**
+ * Sets the specified property as a boolean or clears it if null value given.
+ *
+ * @param name property name
+ * @param value new value or null to clear the property
+ * @return self
+ */
+ protected Config<S> setOrClear(String name, Boolean value) {
+ if (value != null) {
+ object.put(name, value.booleanValue());
+ } else {
+ object.remove(name);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the specified property as an integer.
+ *
+ * @param name property name
+ * @param defaultValue default value if property not set
+ * @return property value or default value
+ */
+ protected int get(String name, int defaultValue) {
+ return object.path(name).asInt(defaultValue);
+ }
+
+ /**
+ * Sets the specified property as an integer or clears it if null value given.
+ *
+ * @param name property name
+ * @param value new value or null to clear the property
+ * @return self
+ */
+ protected Config<S> setOrClear(String name, Integer value) {
+ if (value != null) {
+ object.put(name, value.intValue());
+ } else {
+ object.remove(name);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the specified property as a long.
+ *
+ * @param name property name
+ * @param defaultValue default value if property not set
+ * @return property value or default value
+ */
+ protected long get(String name, long defaultValue) {
+ return object.path(name).asLong(defaultValue);
+ }
+
+ /**
+ * Sets the specified property as a long or clears it if null value given.
+ *
+ * @param name property name
+ * @param value new value or null to clear the property
+ * @return self
+ */
+ protected Config<S> setOrClear(String name, Long value) {
+ if (value != null) {
+ object.put(name, value.longValue());
+ } else {
+ object.remove(name);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the specified property as a double.
+ *
+ * @param name property name
+ * @param defaultValue default value if property not set
+ * @return property value or default value
+ */
+ protected double get(String name, double defaultValue) {
+ return object.path(name).asDouble(defaultValue);
+ }
+
+ /**
+ * Sets the specified property as a double or clears it if null value given.
+ *
+ * @param name property name
+ * @param value new value or null to clear the property
+ * @return self
+ */
+ protected Config<S> setOrClear(String name, Double value) {
+ if (value != null) {
+ object.put(name, value.doubleValue());
+ } else {
+ object.remove(name);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the specified property as an enum.
+ *
+ * @param name property name
+ * @param defaultValue default value if property not set
+ * @param enumClass the enum class
+ * @param <E> type of enum
+ * @return property value or default value
+ */
+ protected <E extends Enum<E>> E get(String name, E defaultValue, Class<E> enumClass) {
+ return Enum.valueOf(enumClass, object.path(name).asText(defaultValue.toString()));
+ }
+
+ /**
+ * Sets the specified property as a double or clears it if null value given.
+ *
+ * @param name property name
+ * @param value new value or null to clear the property
+ * @param <E> type of enum
+ * @return self
+ */
+ protected <E extends Enum> Config<S> setOrClear(String name, E value) {
+ if (value != null) {
+ object.put(name, value.toString());
+ } else {
+ object.remove(name);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the specified array property as a list of items.
+ *
+ * @param name property name
+ * @param function mapper from string to item
+ * @param <T> type of item
+ * @return list of items
+ */
+ protected <T> List<T> getList(String name, Function<String, T> function) {
+ List<T> list = Lists.newArrayList();
+ ArrayNode arrayNode = (ArrayNode) object.path(name);
+ arrayNode.forEach(i -> list.add(function.apply(i.asText())));
+ return list;
+ }
+
+ /**
+ * Sets the specified property as an array of items in a given collection or
+ * clears it if null is given.
+ *
+ * @param name propertyName
+ * @param collection collection of items
+ * @param <T> type of items
+ * @return self
+ */
+ protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
+ if (collection == null) {
+ object.remove(name);
+ } else {
+ ArrayNode arrayNode = mapper.createArrayNode();
+ collection.forEach(i -> arrayNode.add(i.toString()));
+ object.set(name, arrayNode);
+ }
+ return this;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigApplyDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigApplyDelegate.java
new file mode 100644
index 00000000..1160a097
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigApplyDelegate.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Delegate for notification when configuration changes have been applied.
+ */
+@Beta
+public interface ConfigApplyDelegate {
+
+ /**
+ * Processes changes applied to the specified configuration.
+ *
+ * @param config changed configuration
+ */
+ void onApply(Config config);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java
new file mode 100644
index 00000000..25a34025
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigFactory.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Base abstract factory for creating configurations for the specified subject type.
+ *
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ */
+@Beta
+public abstract class ConfigFactory<S, C extends Config<S>> {
+
+ private final SubjectFactory<S> subjectFactory;
+ private final Class<C> configClass;
+ private final String configKey;
+ private final boolean isList;
+
+ /**
+ * Creates a new configuration factory for the specified class of subjects
+ * capable of generating the configurations of the specified class. The
+ * subject and configuration class keys are used merely as keys for use in
+ * composite JSON trees.
+ *
+ * @param subjectFactory subject factory
+ * @param configClass configuration class
+ * @param configKey configuration class key
+ */
+ protected ConfigFactory(SubjectFactory<S> subjectFactory,
+ Class<C> configClass, String configKey) {
+ this(subjectFactory, configClass, configKey, false);
+ }
+
+ /**
+ * Creates a new configuration factory for the specified class of subjects
+ * capable of generating the configurations of the specified class. The
+ * subject and configuration class keys are used merely as keys for use in
+ * composite JSON trees.
+ * <p>
+ * Note that configurations backed by JSON array are not easily extensible
+ * at the top-level as they are inherently limited to holding an ordered
+ * list of items.
+ * </p>
+ *
+ * @param subjectFactory subject factory
+ * @param configClass configuration class
+ * @param configKey configuration class key
+ * @param isList true to indicate backing by JSON array
+ */
+ protected ConfigFactory(SubjectFactory<S> subjectFactory,
+ Class<C> configClass, String configKey,
+ boolean isList) {
+ this.subjectFactory = subjectFactory;
+ this.configClass = configClass;
+ this.configKey = configKey;
+ this.isList = isList;
+ }
+
+ /**
+ * Returns the class of the subject to which this factory applies.
+ *
+ * @return subject type
+ */
+ public SubjectFactory<S> subjectFactory() {
+ return subjectFactory;
+ }
+
+ /**
+ * Returns the class of the configuration which this factory generates.
+ *
+ * @return configuration type
+ */
+ public Class<C> configClass() {
+ return configClass;
+ }
+
+ /**
+ * Returns the unique key (within subject class) of this configuration.
+ * This is primarily aimed for use in composite JSON trees in external
+ * representations and has no bearing on the internal behaviours.
+ *
+ * @return configuration key
+ */
+ public String configKey() {
+ return configKey;
+ }
+
+ /**
+ * Creates a new but uninitialized configuration. Framework will initialize
+ * the configuration via {@link Config#init} method.
+ *
+ * @return new uninitialized configuration
+ */
+ public abstract C createConfig();
+
+ /**
+ * Indicates whether the configuration is a list and should be backed by
+ * a JSON array rather than JSON object.
+ *
+ * @return true if backed by JSON array
+ */
+ public boolean isList() {
+ return isList;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigOperator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigOperator.java
new file mode 100644
index 00000000..505e8b3b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/ConfigOperator.java
@@ -0,0 +1,31 @@
+/*
+ * 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.config;
+
+/**
+ * An interface signifying a class that implements network configuration
+ * information from multiple sources. There is a natural ordering to the
+ * precedence of information, depending on its source:
+ * <ol>
+ * <li>Intents (from applications), which override</li>
+ * <li>Configs (from the network configuration subsystem), which override</li>
+ * <li>Descriptions (from southbound)</li>
+ * </ol>
+ * i.e., for a field representing the same attribute, the value from a Config
+ * entity will be used over that from the Description.
+ */
+public interface ConfigOperator {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java
new file mode 100644
index 00000000..ee9ceadf
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes network configuration event.
+ */
+public class NetworkConfigEvent extends AbstractEvent<NetworkConfigEvent.Type, Object> {
+
+ private final Class configClass;
+
+ /**
+ * Type of network configuration events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a network configuration was registered.
+ */
+ CONFIG_REGISTERED,
+
+ /**
+ * Signifies that a network configuration was unregistered.
+ */
+ CONFIG_UNREGISTERED,
+
+ /**
+ * Signifies that network configuration was added.
+ */
+ CONFIG_ADDED,
+
+ /**
+ * Signifies that network configuration was updated.
+ */
+ CONFIG_UPDATED,
+
+ /**
+ * Signifies that network configuration was removed.
+ */
+ CONFIG_REMOVED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified subject and the
+ * current time.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param configClass configuration class
+ */
+ public NetworkConfigEvent(Type type, Object subject, Class configClass) {
+ super(type, subject);
+ this.configClass = configClass;
+ }
+
+ /**
+ * Creates an event of a given type and for the specified subject and time.
+ *
+ * @param type device event type
+ * @param subject event subject
+ * @param configClass configuration class
+ * @param time occurrence time
+ */
+ public NetworkConfigEvent(Type type, Object subject, Class configClass, long time) {
+ super(type, subject, time);
+ this.configClass = configClass;
+ }
+
+ /**
+ * Returns the class of configuration that has been changed.
+ *
+ * @return configuration class
+ */
+ public Class configClass() {
+ return configClass;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigListener.java
new file mode 100644
index 00000000..73177755
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving network configuration related events.
+ */
+public interface NetworkConfigListener extends EventListener<NetworkConfigEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigRegistry.java
new file mode 100644
index 00000000..b4937d74
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigRegistry.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Set;
+
+/**
+ * Service for tracking network configuration factories. It is the basis for
+ * extensibility to allow various core subsystems or apps to register their
+ * own configuration factories that permit use to inject additional meta
+ * information about how various parts of the network should be viewed and
+ * treated.
+ */
+@Beta
+public interface NetworkConfigRegistry extends NetworkConfigService {
+
+ /**
+ * Registers the specified configuration factory.
+ *
+ * @param configFactory configuration factory
+ */
+ void registerConfigFactory(ConfigFactory configFactory);
+
+ /**
+ * Unregisters the specified configuration factory.
+ *
+ * @param configFactory configuration factory
+ */
+ void unregisterConfigFactory(ConfigFactory configFactory);
+
+ /**
+ * Returns set of all registered configuration factories.
+ *
+ * @return set of config factories
+ */
+ Set<ConfigFactory> getConfigFactories();
+
+ /**
+ * Returns set of all configuration factories registered for the specified
+ * class of subject.
+ *
+ * @param subjectClass subject class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return set of config factories
+ */
+ <S, C extends Config<S>> Set<ConfigFactory<S, C>> getConfigFactories(Class<S> subjectClass);
+
+ /**
+ * Returns the configuration factory that produces the specified class of
+ * configurations.
+ *
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return config factory
+ */
+ <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java
new file mode 100644
index 00000000..c1eed980
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigService.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.annotations.Beta;
+import org.onosproject.event.ListenerService;
+
+import java.util.Set;
+
+/**
+ * Service for tracking network configurations which specify how the discovered
+ * network information should be interpreted and how the core or applications
+ * should act on or configure the network.
+ */
+@Beta
+public interface NetworkConfigService
+ extends ListenerService<NetworkConfigEvent, NetworkConfigListener> {
+
+ /**
+ * Returns the set of subject classes for which configuration may be
+ * available.
+ *
+ * @return set of subject classes
+ */
+ Set<Class> getSubjectClasses();
+
+ /**
+ * Returns the subject factory with the specified key.
+ *
+ * @param subjectKey subject class key
+ * @return subject class
+ */
+ SubjectFactory getSubjectFactory(String subjectKey);
+
+ /**
+ * Returns the subject factory for the specified class.
+ *
+ * @param subjectClass subject class
+ * @return subject class key
+ */
+ SubjectFactory getSubjectFactory(Class subjectClass);
+
+ /**
+ * Returns the configuration class with the specified key.
+ *
+ * @param subjectKey subject class key
+ * @param configKey subject class name
+ * @return subject class
+ */
+ Class<? extends Config> getConfigClass(String subjectKey, String configKey);
+
+ /**
+ * Returns the set of subjects for which some configuration is available.
+ *
+ * @param subjectClass subject class
+ * @param <S> type of subject
+ * @return set of configured subjects
+ */
+ <S> Set<S> getSubjects(Class<S> subjectClass);
+
+ /**
+ * Returns the set of subjects for which the specified configuration is
+ * available.
+ *
+ * @param subjectClass subject class
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return set of configured subjects
+ */
+ <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass);
+
+ /**
+ * Returns all configurations for the specified subject.
+ *
+ * @param subject configuration subject
+ * @param <S> type of subject
+ * @return set of configurations
+ */
+ <S> Set<? extends Config<S>> getConfigs(S subject);
+
+ /**
+ * Returns the configuration for the specified subject and configuration
+ * class if one is available; null otherwise.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration or null if one is not available
+ */
+ <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass);
+
+ /**
+ * Creates a new configuration for the specified subject and configuration
+ * class. If one already exists, it is simply returned.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration or null if one is not available
+ */
+ <S, C extends Config<S>> C addConfig(S subject, Class<C> configClass);
+
+ /**
+ * Applies configuration for the specified subject and configuration
+ * class using the raw JSON object. If configuration already exists, it
+ * will be updated.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param json raw JSON node containing the configuration data
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration or null if one is not available
+ */
+ <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass,
+ JsonNode json);
+
+ /**
+ * Clears any configuration for the specified subject and configuration
+ * class. If one does not exist, this call has no effect.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ */
+ <S, C extends Config<S>> void removeConfig(S subject, Class<C> configClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java
new file mode 100644
index 00000000..9dd66e8d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStore.java
@@ -0,0 +1,130 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Mechanism for distributing and storing network configuration information.
+ */
+public interface NetworkConfigStore extends Store<NetworkConfigEvent, NetworkConfigStoreDelegate> {
+
+ /**
+ * Adds a new configuration factory.
+ *
+ * @param configFactory configuration factory to add
+ */
+ void addConfigFactory(ConfigFactory configFactory);
+
+ /**
+ * Removes a configuration factory.
+ *
+ * @param configFactory configuration factory to remove
+ */
+ void removeConfigFactory(ConfigFactory configFactory);
+
+ /**
+ * Returns the configuration factory for the specified configuration class.
+ *
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration factory or null
+ */
+ <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass);
+
+ /**
+ * Returns set of subjects of the specified class, which have some
+ * network configuration associated with them.
+ *
+ * @param subjectClass subject class
+ * @param <S> type of subject
+ * @return set of subject
+ */
+ <S> Set<S> getSubjects(Class<S> subjectClass);
+
+ /**
+ * Returns set of subjects of the specified class, which have the
+ * specified class of network configuration associated with them.
+ *
+ * @param subjectClass subject class
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return set of subject
+ */
+ <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass);
+
+ /**
+ * Returns set of configuration classes available for the specified subject.
+ *
+ * @param subject configuration subject
+ * @param <S> type of subject
+ * @return set of configuration classes
+ */
+ <S> Set<Class<? extends Config<S>>> getConfigClasses(S subject);
+
+ /**
+ * Get the configuration of the given class and for the specified subject.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration object
+ */
+ <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass);
+
+ /**
+ * Creates a new configuration of the given class for the specified subject.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration object
+ */
+ <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass);
+
+ /**
+ * Applies configuration for the specified subject and configuration
+ * class using the raw JSON object. If configuration already exists, it
+ * will be updated.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param json raw JSON node containing the configuration data
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ * @return configuration object
+ */
+ <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass,
+ JsonNode json);
+
+ /**
+ * Clears the configuration of the given class for the specified subject.
+ *
+ * @param subject configuration subject
+ * @param configClass configuration class
+ * @param <S> type of subject
+ * @param <C> type of configuration
+ */
+ <S, C extends Config<S>> void clearConfig(S subject, Class<C> configClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStoreDelegate.java
new file mode 100644
index 00000000..15d3d3e8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/NetworkConfigStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Network configuration store delegate abstraction.
+ */
+public interface NetworkConfigStoreDelegate extends StoreDelegate<NetworkConfigEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/SubjectFactory.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/SubjectFactory.java
new file mode 100644
index 00000000..cd2db344
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/SubjectFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Base abstract factory for creating configuration subjects from their
+ * string key image.
+ *
+ * @param <S> subject class
+ */
+@Beta
+public abstract class SubjectFactory<S> {
+
+ private final Class<S> subjectClass;
+ private final String subjectKey;
+
+ /**
+ * Creates a new configuration factory for the specified class of subjects
+ * capable of generating the configurations of the specified class. The
+ * subject and configuration class keys are used merely as keys for use in
+ * composite JSON trees.
+ *
+ * @param subjectClass subject class
+ * @param subjectKey subject class key
+ */
+ protected SubjectFactory(Class<S> subjectClass, String subjectKey) {
+ this.subjectClass = subjectClass;
+ this.subjectKey = subjectKey;
+ }
+
+ /**
+ * Returns the class of the subject to which this factory applies.
+ *
+ * @return subject type
+ */
+ public Class<S> subjectClass() {
+ return subjectClass;
+ }
+
+ /**
+ * Returns the unique key of this configuration subject class.
+ * This is primarily aimed for use in composite JSON trees in external
+ * representations and has no bearing on the internal behaviours.
+ *
+ * @return configuration key
+ */
+ public String subjectKey() {
+ return subjectKey;
+ }
+
+ /**
+ * Creates a configuration subject from its key image.
+ *
+ * @param subjectKey subject class key
+ * @return configuration subject
+ */
+ public abstract S createSubject(String subjectKey);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/AllowedEntityConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/AllowedEntityConfig.java
new file mode 100644
index 00000000..6e6663c4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/AllowedEntityConfig.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.net.config.basics;
+
+import org.onosproject.net.config.Config;
+
+/**
+ * Base abstraction for network entities for which admission into control
+ * domain can be selectively configured, e.g. devices, end-stations, links
+ */
+public abstract class AllowedEntityConfig<S> extends Config<S> {
+
+ private static final String ALLOWED = "allowed";
+
+ /**
+ * Indicates whether the element is allowed for admission into the control
+ * domain.
+ *
+ * @return true if element is allowed
+ */
+ public boolean isAllowed() {
+ return get(ALLOWED, true);
+ }
+
+ /**
+ * Specifies whether the element is to be allowed for admission into the
+ * control domain.
+ *
+ * @param isAllowed true to allow; false to forbid; null to clear
+ * @return self
+ */
+ public AllowedEntityConfig isAllowed(Boolean isAllowed) {
+ return (AllowedEntityConfig) setOrClear(ALLOWED, isAllowed);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
new file mode 100644
index 00000000..fd8bfa3e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.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.config.basics;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Basic configuration for network infrastructure devices.
+ */
+public class BasicDeviceConfig extends BasicElementConfig<DeviceId> {
+
+ public static final String TYPE = "type";
+ public static final String DRIVER = "driver";
+
+ /**
+ * Returns the device type.
+ *
+ * @return device type override
+ */
+ public Device.Type type() {
+ return get(TYPE, Device.Type.SWITCH, Device.Type.class);
+ }
+
+ /**
+ * Sets the device type.
+ *
+ * @param type device type override
+ * @return self
+ */
+ public BasicDeviceConfig type(Device.Type type) {
+ return (BasicDeviceConfig) setOrClear(TYPE, type);
+ }
+
+ /**
+ * Returns the device driver name.
+ *
+ * @return driver name of null if not set
+ */
+ public String driver() {
+ return get(DRIVER, subject.toString());
+ }
+
+ /**
+ * Sets the driver name.
+ *
+ * @param driverName new driver name; null to clear
+ * @return self
+ */
+ public BasicElementConfig driver(String driverName) {
+ return (BasicElementConfig) setOrClear(DRIVER, driverName);
+ }
+
+ // TODO: device port meta-data to be configured via BasicPortsConfig
+ // TODO: device credentials/keys
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
new file mode 100644
index 00000000..7b3248c9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
@@ -0,0 +1,130 @@
+/*
+ * 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.basics;
+
+/**
+ * Basic configuration for network elements, e.g. devices, hosts. Such elements
+ * can have a friendly name, geo-coordinates, logical rack coordinates and
+ * an owner entity.
+ */
+public abstract class BasicElementConfig<S> extends AllowedEntityConfig<S> {
+
+ public static final String NAME = "name";
+
+ public static final String LATITUDE = "latitude";
+ public static final String LONGITUDE = "longitude";
+
+ public static final String RACK_ADDRESS = "rackAddress";
+ public static final String OWNER = "owner";
+
+ protected static final double DEFAULT_COORD = -1.0;
+
+ /**
+ * Returns friendly label for the element.
+ *
+ * @return friendly label or element id itself if not set
+ */
+ public String name() {
+ return get(NAME, subject.toString());
+ }
+
+ /**
+ * Sets friendly label for the element.
+ *
+ * @param name new friendly label; null to clear
+ * @return self
+ */
+ public BasicElementConfig name(String name) {
+ return (BasicElementConfig) setOrClear(NAME, name);
+ }
+
+ /**
+ * Returns element latitude.
+ *
+ * @return element latitude; -1 if not set
+ */
+ public double latitude() {
+ return get(LATITUDE, DEFAULT_COORD);
+ }
+
+ /**
+ * Sets the element latitude.
+ *
+ * @param latitude new latitude; null to clear
+ * @return self
+ */
+ public BasicElementConfig latitude(Double latitude) {
+ return (BasicElementConfig) setOrClear(LATITUDE, latitude);
+ }
+
+ /**
+ * Returns element latitude.
+ *
+ * @return element latitude; -1 if not set
+ */
+ public double longitude() {
+ return get(LONGITUDE, DEFAULT_COORD);
+ }
+
+ /**
+ * Sets the element longitude.
+ *
+ * @param longitude new longitude; null to clear
+ * @return self
+ */
+ public BasicElementConfig longitude(Double longitude) {
+ return (BasicElementConfig) setOrClear(LONGITUDE, longitude);
+ }
+
+ /**
+ * Returns the element rack address.
+ *
+ * @return rack address; null if not set
+ */
+ public String rackAddress() {
+ return get(RACK_ADDRESS, null);
+ }
+
+ /**
+ * Sets element rack address.
+ *
+ * @param address new rack address; null to clear
+ * @return self
+ */
+ public BasicElementConfig rackAddress(String address) {
+ return (BasicElementConfig) setOrClear(RACK_ADDRESS, address);
+ }
+
+ /**
+ * Returns owner of the element.
+ *
+ * @return owner or null if not set
+ */
+ public String owner() {
+ return get(OWNER, null);
+ }
+
+ /**
+ * Sets the owner of the element.
+ *
+ * @param owner new owner; null to clear
+ * @return self
+ */
+ public BasicElementConfig owner(String owner) {
+ return (BasicElementConfig) setOrClear(OWNER, owner);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
new file mode 100644
index 00000000..2fe2b2c0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.basics;
+
+import org.onosproject.net.HostId;
+
+/**
+ * Basic configuration for network end-station hosts.
+ */
+public class BasicHostConfig extends BasicElementConfig<HostId> {
+
+ // TODO: determine what aspects of configuration to add for hosts
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java
new file mode 100644
index 00000000..b6068ee7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java
@@ -0,0 +1,90 @@
+/*
+ * 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.basics;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import java.time.Duration;
+
+/**
+ * Basic configuration for network infrastructure link.
+ */
+public class BasicLinkConfig extends AllowedEntityConfig<LinkKey> {
+
+ public static final String TYPE = "type";
+ public static final String LATENCY = "latency";
+ public static final String BANDWIDTH = "bandwidth";
+
+ /**
+ * Returns the link type.
+ *
+ * @return link type override
+ */
+ public Link.Type type() {
+ return get(TYPE, Link.Type.DIRECT, Link.Type.class);
+ }
+
+ /**
+ * Sets the link type.
+ *
+ * @param type link type override
+ * @return self
+ */
+ public BasicLinkConfig type(Link.Type type) {
+ return (BasicLinkConfig) setOrClear(TYPE, type);
+ }
+
+ /**
+ * Returns link latency in terms of nanos.
+ *
+ * @return link latency; -1 if not set
+ */
+ public Duration latency() {
+ return Duration.ofNanos(get(LATENCY, -1));
+ }
+
+ /**
+ * Sets the link latency.
+ *
+ * @param latency new latency; null to clear
+ * @return self
+ */
+ public BasicLinkConfig latency(Duration latency) {
+ Long nanos = latency == null ? null : latency.toNanos();
+ return (BasicLinkConfig) setOrClear(LATENCY, nanos);
+ }
+
+ /**
+ * Returns link bandwidth in terms of Mbps.
+ *
+ * @return link bandwidth; -1 if not set
+ */
+ public long bandwidth() {
+ return get(BANDWIDTH, -1);
+ }
+
+ /**
+ * Sets the link bandwidth.
+ *
+ * @param bandwidth new bandwidth; null to clear
+ * @return self
+ */
+ public BasicLinkConfig bandwidth(Long bandwidth) {
+ return (BasicLinkConfig) setOrClear(BANDWIDTH, bandwidth);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
new file mode 100644
index 00000000..b06c4228
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
@@ -0,0 +1,175 @@
+package org.onosproject.net.config.basics;
+
+import java.util.Optional;
+
+import org.onosproject.net.config.Config;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Port;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+
+/**
+ * Configurations for an optical port on a device.
+ */
+public class OpticalPortConfig extends Config<ConnectPoint> {
+ // optical type {OMS, OCH, ODUClt, fiber}
+ public static final String TYPE = "type";
+
+ // port name. "name" is the alphanumeric name of the port, but "port" refers
+ // to the port number used as a name string (i.e., for ports without
+ // alphanumeric names).
+ public static final String NAME = "name";
+ public static final String PORT = "port";
+ public static final String STATIC_PORT = "staticPort";
+ public static final String STATIC_LAMBDA = "staticLambda";
+
+ // **Linc-OE : remove if it's not needed after all.**
+ public static final String SPEED = "speed";
+
+ /**
+ * Returns the Enum value representing the type of port.
+ *
+ * @return the port type, or null if invalid or unset
+ */
+ public Port.Type type() {
+ JsonNode type = object.path(TYPE);
+ if (type.isMissingNode()) {
+ return null;
+ }
+ return Port.Type.valueOf(type.asText());
+ }
+
+ /**
+ * Returns the port name associated with this port configuration. The Name
+ * is an alphanumeric string.
+ *
+ * @return the name of this port, else, an empty string
+ */
+ public String name() {
+ return getStringValue(NAME);
+ }
+
+ /**
+ * Returns a stringified representation of the port number, configured in
+ * some port types without an alphanumeric name as the port name.
+ *
+ * @return A string representation of the port number
+ */
+ public String numberName() {
+ return getStringValue(PORT);
+ }
+
+ /**
+ * Returns the string-representation of name of the output port. This is
+ * usually an OMS port for an OCH input ports, or an OCH port for ODU input
+ * ports.
+ *
+ * @return the name of this port, else, an empty string
+ */
+ public String staticPort() {
+ return getStringValue(STATIC_PORT);
+ }
+
+ private String getStringValue(String field) {
+ JsonNode name = object.path(field);
+ return name.isMissingNode() ? "" : name.asText();
+ }
+
+ /**
+ * Returns the output lambda configured for this port. The lambda value is
+ * expressed as a frequency value. If the port type doesn't have a notion of
+ * lambdas, this returns an empty Optional.
+ *
+ * @return an Optional that may contain a frequency value.
+ */
+ public Optional<Long> staticLambda() {
+ JsonNode sl = object.path(STATIC_LAMBDA);
+ if (sl.isMissingNode()) {
+ return Optional.empty();
+ }
+ return Optional.of(sl.asLong());
+ }
+
+ /**
+ * Returns the port speed configured for this port. If the port doesn't have
+ * a notion of speed, this returns an empty Optional.
+ *
+ * @return a port speed value whose default is 0.
+ */
+ public Optional<Integer> speed() {
+ JsonNode s = object.path(SPEED);
+ if (s.isMissingNode()) {
+ return Optional.empty();
+ }
+ return Optional.of(s.asInt());
+ }
+
+ /**
+ * Sets the port type, or updates it if it's already set. A null argument removes
+ * this field.
+ *
+ * @param type the port type
+ * @return this OpticalPortConfig instance
+ */
+ public OpticalPortConfig portType(Port.Type type) {
+ // if unspecified, ideally fall back on FIBER or PACKET.
+ String pt = (type == null) ? null : type.toString();
+ return (OpticalPortConfig) setOrClear(TYPE, pt);
+ }
+
+ /**
+ * Sets the port name, or updates it if already set. A null argument removes
+ * this field.
+ *
+ * @param name the port's name
+ * @return this OpticalPortConfig instance
+ */
+ public OpticalPortConfig portName(String name) {
+ return (OpticalPortConfig) setOrClear(NAME, name);
+ }
+
+ /**
+ * Sets the port name from port number, or updates it if already set. A null
+ * argument removes this field.
+ *
+ * @param name the port number, to be used as name
+ * @return this OpticalPortConfig instance
+ */
+ public OpticalPortConfig portNumberName(Long name) {
+ return (OpticalPortConfig) setOrClear(PORT, name);
+ }
+
+ /**
+ * Sets the output port name, or updates it if already set. A null argument
+ * removes this field.
+ *
+ * @param name the output port's name
+ * @return this OpticalPortConfig instance
+ */
+ public OpticalPortConfig staticPort(String name) {
+ return (OpticalPortConfig) setOrClear(STATIC_PORT, name);
+ }
+
+ /**
+ * Sets the output lambda index, or updates it if already set. A null argument
+ * removes this field.
+ *
+ * @param index the output lambda
+ * @return this OpticalPortConfig instance
+ */
+ public OpticalPortConfig staticLambda(Long index) {
+ return (OpticalPortConfig) setOrClear(STATIC_LAMBDA, index);
+ }
+
+ /**
+ * Sets the port speed, or updates it if already set. A null argument
+ * removes this field.
+ *
+ * @param bw the port bandwidth
+ * @return this OpticalPortConfig instance
+ */
+ public OpticalPortConfig speed(Integer bw) {
+ return (OpticalPortConfig) setOrClear(SPEED, bw);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/SubjectFactories.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/SubjectFactories.java
new file mode 100644
index 00000000..884f2e20
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/SubjectFactories.java
@@ -0,0 +1,93 @@
+/*
+ * 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.basics;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+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.SubjectFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Set of subject factories for potential configuration subjects.
+ */
+public final class SubjectFactories {
+
+ // Construction forbidden
+ private SubjectFactories() {
+ }
+
+ // Required for resolving application identifiers
+ private static CoreService coreService;
+
+ public static final SubjectFactory<ApplicationId> APP_SUBJECT_FACTORY =
+ new SubjectFactory<ApplicationId>(ApplicationId.class, "apps") {
+ @Override
+ public ApplicationId createSubject(String key) {
+ return coreService.registerApplication(key);
+ }
+ };
+
+ public static final SubjectFactory<DeviceId> DEVICE_SUBJECT_FACTORY =
+ new SubjectFactory<DeviceId>(DeviceId.class, "devices") {
+ @Override
+ public DeviceId createSubject(String key) {
+ return DeviceId.deviceId(key);
+ }
+ };
+
+ public static final SubjectFactory<ConnectPoint> CONNECT_POINT_SUBJECT_FACTORY =
+ new SubjectFactory<ConnectPoint>(ConnectPoint.class, "ports") {
+ @Override
+ public ConnectPoint createSubject(String key) {
+ return ConnectPoint.deviceConnectPoint(key);
+ }
+ };
+
+ public static final SubjectFactory<HostId> HOST_SUBJECT_FACTORY =
+ new SubjectFactory<HostId>(HostId.class, "hosts") {
+ @Override
+ public HostId createSubject(String key) {
+ return HostId.hostId(key);
+ }
+ };
+
+ public static final SubjectFactory<LinkKey> LINK_SUBJECT_FACTORY =
+ new SubjectFactory<LinkKey>(LinkKey.class, "links") {
+ @Override
+ public LinkKey createSubject(String key) {
+ String[] cps = key.split("-");
+ checkArgument(cps.length == 2, "Incorrect link key format: %s", key);
+ return LinkKey.linkKey(ConnectPoint.deviceConnectPoint(cps[0]),
+ ConnectPoint.deviceConnectPoint(cps[1]));
+ }
+ };
+
+ /**
+ * Provides reference to the core service, which is required for
+ * application subject factory.
+ *
+ * @param service core service reference
+ */
+ public static void setCoreService(CoreService service) {
+ coreService = service;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/package-info.java
new file mode 100644
index 00000000..4d0f27e9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/basics/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.
+ */
+
+/**
+ * Various basic builtin network configurations.
+ */
+package org.onosproject.net.config.basics; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/package-info.java
new file mode 100644
index 00000000..f300717a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/config/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 tracking network environment configuration.
+ */
+package org.onosproject.net.config;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultDeviceDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultDeviceDescription.java
new file mode 100644
index 00000000..3a8c8c1f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultDeviceDescription.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.SparseAnnotations;
+import org.onlab.packet.ChassisId;
+
+import java.net.URI;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.Device.Type;
+
+/**
+ * Default implementation of immutable device description entity.
+ */
+public class DefaultDeviceDescription extends AbstractDescription
+ implements DeviceDescription {
+ private final URI uri;
+ private final Type type;
+ private final String manufacturer;
+ private final String hwVersion;
+ private final String swVersion;
+ private final String serialNumber;
+ private final ChassisId chassisId;
+
+ /**
+ * Creates a device description using the supplied information.
+ *
+ * @param uri device URI
+ * @param type device type
+ * @param manufacturer device manufacturer
+ * @param hwVersion device HW version
+ * @param swVersion device SW version
+ * @param serialNumber device serial number
+ * @param chassis chassis id
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultDeviceDescription(URI uri, Type type, String manufacturer,
+ String hwVersion, String swVersion,
+ String serialNumber, ChassisId chassis,
+ SparseAnnotations... annotations) {
+ super(annotations);
+ this.uri = checkNotNull(uri, "Device URI cannot be null");
+ this.type = checkNotNull(type, "Device type cannot be null");
+ this.manufacturer = manufacturer;
+ this.hwVersion = hwVersion;
+ this.swVersion = swVersion;
+ this.serialNumber = serialNumber;
+ this.chassisId = chassis;
+ }
+
+ /**
+ * Creates a device description using the supplied information.
+ * @param base DeviceDescription to basic information
+ * @param annotations Annotations to use.
+ */
+ public DefaultDeviceDescription(DeviceDescription base,
+ SparseAnnotations... annotations) {
+ this(base.deviceURI(), base.type(), base.manufacturer(),
+ base.hwVersion(), base.swVersion(), base.serialNumber(),
+ base.chassisId(), annotations);
+ }
+
+ /**
+ * Creates a device description using the supplied information.
+ * @param base DeviceDescription to basic information (except for type)
+ * @param type device type
+ * @param annotations Annotations to use.
+ */
+ public DefaultDeviceDescription(DeviceDescription base, Type type, SparseAnnotations... annotations) {
+ this(base.deviceURI(), type, base.manufacturer(),
+ base.hwVersion(), base.swVersion(), base.serialNumber(),
+ base.chassisId(), annotations);
+ }
+
+ @Override
+ public URI deviceURI() {
+ return uri;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public String manufacturer() {
+ return manufacturer;
+ }
+
+ @Override
+ public String hwVersion() {
+ return hwVersion;
+ }
+
+ @Override
+ public String swVersion() {
+ return swVersion;
+ }
+
+ @Override
+ public String serialNumber() {
+ return serialNumber;
+ }
+
+ @Override
+ public ChassisId chassisId() {
+ return chassisId;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("uri", uri).add("type", type).add("mfr", manufacturer)
+ .add("hw", hwVersion).add("sw", swVersion)
+ .add("serial", serialNumber)
+ .toString();
+ }
+
+ // default constructor for serialization
+ private DefaultDeviceDescription() {
+ this.uri = null;
+ this.type = null;
+ this.manufacturer = null;
+ this.hwVersion = null;
+ this.swVersion = null;
+ this.serialNumber = null;
+ this.chassisId = null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortDescription.java
new file mode 100644
index 00000000..572d201c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortDescription.java
@@ -0,0 +1,120 @@
+/*
+ * 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.device;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+
+import static org.onosproject.net.Port.Type;
+
+/**
+ * Default implementation of immutable port description.
+ */
+public class DefaultPortDescription extends AbstractDescription
+ implements PortDescription {
+
+ private static final long DEFAULT_SPEED = 1_000;
+
+ private final PortNumber number;
+ private final boolean isEnabled;
+ private final Type type;
+ private final long portSpeed;
+
+ /**
+ * Creates a port description using the supplied information.
+ *
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultPortDescription(PortNumber number, boolean isEnabled,
+ SparseAnnotations... annotations) {
+ this(number, isEnabled, Type.COPPER, DEFAULT_SPEED, annotations);
+ }
+
+ /**
+ * Creates a port description using the supplied information.
+ *
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param type port type
+ * @param portSpeed port speed in Mbps
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultPortDescription(PortNumber number, boolean isEnabled,
+ Type type, long portSpeed,
+ SparseAnnotations...annotations) {
+ super(annotations);
+ this.number = number;
+ this.isEnabled = isEnabled;
+ this.type = type;
+ this.portSpeed = portSpeed;
+ }
+
+ // Default constructor for serialization
+ private DefaultPortDescription() {
+ this.number = null;
+ this.isEnabled = false;
+ this.portSpeed = DEFAULT_SPEED;
+ this.type = Type.COPPER;
+ }
+
+ /**
+ * Creates a port description using the supplied information.
+ *
+ * @param base PortDescription to get basic information from
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultPortDescription(PortDescription base,
+ SparseAnnotations annotations) {
+ this(base.portNumber(), base.isEnabled(), base.type(), base.portSpeed(),
+ annotations);
+ }
+
+ @Override
+ public PortNumber portNumber() {
+ return number;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public long portSpeed() {
+ return portSpeed;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("number", number)
+ .add("isEnabled", isEnabled)
+ .add("type", type)
+ .add("portSpeed", portSpeed)
+ .add("annotations", annotations())
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java
new file mode 100644
index 00000000..540a945f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java
@@ -0,0 +1,346 @@
+/*
+ * 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.device;
+
+import org.onosproject.net.DeviceId;
+
+/**
+ * Default implementation of immutable port statistics.
+ */
+public final class DefaultPortStatistics implements PortStatistics {
+
+ private final DeviceId deviceId;
+ private final int port;
+ private final long packetsReceived;
+ private final long packetsSent;
+ private final long bytesReceived;
+ private final long bytesSent;
+ private final long packetsRxDropped;
+ private final long packetsTxDropped;
+ private final long packetsRxErrors;
+ private final long packetsTxErrors;
+ private final long durationSec;
+ private final long durationNano;
+
+ private DefaultPortStatistics(DeviceId deviceId,
+ int port,
+ long packetsReceived,
+ long packetsSent,
+ long bytesReceived,
+ long bytesSent,
+ long packetsRxDropped,
+ long packetsTxDropped,
+ long packetsRxErrors,
+ long packetsTxErrors,
+ long durationSec,
+ long durationNano) {
+ this.deviceId = deviceId;
+ this.port = port;
+ this.packetsReceived = packetsReceived;
+ this.packetsSent = packetsSent;
+ this.bytesReceived = bytesReceived;
+ this.bytesSent = bytesSent;
+ this.packetsRxDropped = packetsRxDropped;
+ this.packetsTxDropped = packetsTxDropped;
+ this.packetsRxErrors = packetsRxErrors;
+ this.packetsTxErrors = packetsTxErrors;
+ this.durationSec = durationSec;
+ this.durationNano = durationNano;
+ }
+
+ // Constructor for serializer
+ private DefaultPortStatistics() {
+ this.deviceId = null;
+ this.port = 0;
+ this.packetsReceived = 0;
+ this.packetsSent = 0;
+ this.bytesReceived = 0;
+ this.bytesSent = 0;
+ this.packetsRxDropped = 0;
+ this.packetsTxDropped = 0;
+ this.packetsRxErrors = 0;
+ this.packetsTxErrors = 0;
+ this.durationSec = 0;
+ this.durationNano = 0;
+ }
+
+ /**
+ * Creates a builder for DefaultPortStatistics object.
+ *
+ * @return builder object for DefaultPortStatistics object
+ */
+ public static DefaultPortStatistics.Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public int port() {
+ return this.port;
+ }
+
+ @Override
+ public long packetsReceived() {
+ return this.packetsReceived;
+ }
+
+ @Override
+ public long packetsSent() {
+ return this.packetsSent;
+ }
+
+ @Override
+ public long bytesReceived() {
+ return this.bytesReceived;
+ }
+
+ @Override
+ public long bytesSent() {
+ return this.bytesSent;
+ }
+
+ @Override
+ public long packetsRxDropped() {
+ return this.packetsRxDropped;
+ }
+
+ @Override
+ public long packetsTxDropped() {
+ return this.packetsTxDropped;
+ }
+
+ @Override
+ public long packetsRxErrors() {
+ return this.packetsRxErrors;
+ }
+
+ @Override
+ public long packetsTxErrors() {
+ return this.packetsTxErrors;
+ }
+
+ @Override
+ public long durationSec() {
+ return this.durationSec;
+ }
+
+ @Override
+ public long durationNano() {
+ return this.durationNano;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("device: " + deviceId + ", ");
+
+ sb.append("port: " + this.port + ", ");
+ sb.append("pktRx: " + this.packetsReceived + ", ");
+ sb.append("pktTx: " + this.packetsSent + ", ");
+ sb.append("byteRx: " + this.bytesReceived + ", ");
+ sb.append("byteTx: " + this.bytesSent + ", ");
+ sb.append("pktRxErr: " + this.packetsRxErrors + ", ");
+ sb.append("pktTxErr: " + this.packetsTxErrors + ", ");
+ sb.append("pktRxDrp: " + this.packetsRxDropped + ", ");
+ sb.append("pktTxDrp: " + this.packetsTxDropped);
+
+ return sb.toString();
+ }
+
+ public static final class Builder {
+
+ DeviceId deviceId;
+ int port;
+ long packetsReceived;
+ long packetsSent;
+ long bytesReceived;
+ long bytesSent;
+ long packetsRxDropped;
+ long packetsTxDropped;
+ long packetsRxErrors;
+ long packetsTxErrors;
+ long durationSec;
+ long durationNano;
+
+ private Builder() {
+
+ }
+
+ /**
+ * Sets port number.
+ *
+ * @param port port number
+ * @return builder object
+ */
+ public Builder setPort(int port) {
+ this.port = port;
+
+ return this;
+ }
+
+ /**
+ * Sets the device identifier.
+ *
+ * @param deviceId device identifier
+ * @return builder object
+ */
+ public Builder setDeviceId(DeviceId deviceId) {
+ this.deviceId = deviceId;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of packet received.
+ *
+ * @param packets number of packets received
+ * @return builder object
+ */
+ public Builder setPacketsReceived(long packets) {
+ packetsReceived = packets;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of packets sent.
+ *
+ * @param packets number of packets sent
+ * @return builder object
+ */
+ public Builder setPacketsSent(long packets) {
+ packetsSent = packets;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of received bytes.
+ *
+ * @param bytes number of received bytes.
+ * @return builder object
+ */
+ public Builder setBytesReceived(long bytes) {
+ bytesReceived = bytes;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of sent bytes.
+ *
+ * @param bytes number of sent bytes
+ * @return builder object
+ */
+ public Builder setBytesSent(long bytes) {
+ bytesSent = bytes;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of packets dropped by RX.
+ *
+ * @param packets number of packets dropped by RX
+ * @return builder object
+ */
+ public Builder setPacketsRxDropped(long packets) {
+ packetsRxDropped = packets;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of packets dropped by TX.
+ *
+ * @param packets number of packets
+ * @return builder object
+ */
+ public Builder setPacketsTxDropped(long packets) {
+ packetsTxDropped = packets;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of receive errors.
+ *
+ * @param packets number of receive errors
+ * @return builder object
+ */
+ public Builder setPacketsRxErrors(long packets) {
+ packetsRxErrors = packets;
+
+ return this;
+ }
+
+ /**
+ * Sets the number of transmit errors.
+ *
+ * @param packets number of transmit errors
+ * @return builder object
+ */
+ public Builder setPacketsTxErrors(long packets) {
+ packetsTxErrors = packets;
+
+ return this;
+ }
+
+ /**
+ * Sets the time port has been alive in seconds.
+ *
+ * @param sec time port has been alive in seconds
+ * @return builder object
+ */
+ public Builder setDurationSec(long sec) {
+ durationSec = sec;
+
+ return this;
+ }
+
+ /**
+ * Sets the time port has been alive in nano seconds.
+ *
+ * @param nano time port has been alive in nano seconds
+ * @return builder object
+ */
+ public Builder setDurationNano(long nano) {
+ durationNano = nano;
+
+ return this;
+ }
+
+ /**
+ * Creates a PortStatistics object.
+ *
+ * @return DefaultPortStatistics object
+ */
+ public DefaultPortStatistics build() {
+ return new DefaultPortStatistics(
+ deviceId,
+ port,
+ packetsReceived,
+ packetsSent,
+ bytesReceived,
+ bytesSent,
+ packetsRxDropped,
+ packetsTxDropped,
+ packetsRxErrors,
+ packetsTxErrors,
+ durationSec,
+ durationNano);
+ }
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java
new file mode 100644
index 00000000..500b6359
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+
+/**
+ * Service for administering the inventory of infrastructure devices.
+ */
+public interface DeviceAdminService extends DeviceService {
+
+ /**
+ * Removes the device with the specified identifier.
+ *
+ * @param deviceId device identifier
+ */
+ void removeDevice(DeviceId deviceId);
+
+ // TODO: add ability to administratively suspend/resume device
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceClockService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceClockService.java
new file mode 100644
index 00000000..5391999a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceClockService.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.net.device;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Timestamp;
+
+/**
+ * Interface for a logical clock service that vends per device timestamps.
+ */
+public interface DeviceClockService {
+
+ /**
+ * Checks if this service can issue Timestamp for specified device.
+ *
+ * @param deviceId device identifier.
+ * @return true if timestamp can be issued for specified device
+ */
+ boolean isTimestampAvailable(DeviceId deviceId);
+
+ /**
+ * Returns a new timestamp for the specified deviceId.
+ *
+ * @param deviceId device identifier.
+ * @return timestamp.
+ */
+ Timestamp getTimestamp(DeviceId deviceId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceDescription.java
new file mode 100644
index 00000000..64b84b5a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceDescription.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import org.onosproject.net.Description;
+import org.onosproject.net.Device;
+import org.onlab.packet.ChassisId;
+
+import java.net.URI;
+
+/**
+ * Carrier of immutable information about a device.
+ */
+public interface DeviceDescription extends Description {
+
+ /**
+ * Protocol/provider specific URI that can be used to encode the identity
+ * information required to communicate with the device externally, e.g.
+ * datapath ID.
+ *
+ * @return provider specific URI for the device
+ */
+ URI deviceURI();
+
+ /**
+ * Returns the type of the infrastructure device.
+ *
+ * @return type of the device
+ */
+ Device.Type type();
+
+ /**
+ * Returns the device manufacturer name.
+ *
+ * @return manufacturer name
+ */
+ String manufacturer();
+
+ /**
+ * Returns the device hardware version.
+ *
+ * @return hardware version
+ */
+ String hwVersion();
+
+ /**
+ * Returns the device software version.
+ *
+ * @return software version
+ */
+ String swVersion();
+
+ /**
+ * Returns the device serial number.
+ *
+ * @return serial number
+ */
+ String serialNumber();
+
+ /**
+ * Returns a device chassis id.
+ *
+ * @return chassis id
+ */
+ ChassisId chassisId();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceEvent.java
new file mode 100644
index 00000000..18ab046f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceEvent.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import org.joda.time.LocalDateTime;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Describes infrastructure device event.
+ */
+public class DeviceEvent extends AbstractEvent<DeviceEvent.Type, Device> {
+
+ private final Port port;
+
+ /**
+ * Type of device events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a new device has been detected.
+ */
+ DEVICE_ADDED,
+
+ /**
+ * Signifies that some device attributes have changed; excludes
+ * availability changes.
+ */
+ DEVICE_UPDATED,
+
+ /**
+ * Signifies that a device has been removed.
+ */
+ DEVICE_REMOVED,
+
+ /**
+ * Signifies that a device has been administratively suspended.
+ */
+ DEVICE_SUSPENDED,
+
+ /**
+ * Signifies that a device has come online or has gone offline.
+ */
+ DEVICE_AVAILABILITY_CHANGED,
+
+ /**
+ * Signifies that a port has been added.
+ */
+ PORT_ADDED,
+
+ /**
+ * Signifies that a port has been updated.
+ */
+ PORT_UPDATED,
+
+ /**
+ * Signifies that a port has been removed.
+ */
+ PORT_REMOVED,
+
+ /**
+ * Signifies that port statistics has been updated.
+ */
+ PORT_STATS_UPDATED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified device and the
+ * current time.
+ *
+ * @param type device event type
+ * @param device event device subject
+ */
+ public DeviceEvent(Type type, Device device) {
+ this(type, device, null);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified device, port
+ * and the current time.
+ *
+ * @param type device event type
+ * @param device event device subject
+ * @param port optional port subject
+ */
+ public DeviceEvent(Type type, Device device, Port port) {
+ super(type, device);
+ this.port = port;
+ }
+
+ /**
+ * Creates an event of a given type and for the specified device and time.
+ *
+ * @param type device event type
+ * @param device event device subject
+ * @param port optional port subject
+ * @param time occurrence time
+ */
+ public DeviceEvent(Type type, Device device, Port port, long time) {
+ super(type, device, time);
+ this.port = port;
+ }
+
+ /**
+ * Returns the port subject.
+ *
+ * @return port subject or null if the event is not port specific.
+ */
+ public Port port() {
+ return port;
+ }
+
+ @Override
+ public String toString() {
+ if (port == null) {
+ return super.toString();
+ }
+ return toStringHelper(this)
+ .add("time", new LocalDateTime(time()))
+ .add("type", type())
+ .add("subject", subject())
+ .add("port", port)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceListener.java
new file mode 100644
index 00000000..c9809b81
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.device;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving infrastructure device related events.
+ */
+public interface DeviceListener extends EventListener<DeviceEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProvider.java
new file mode 100644
index 00000000..d8adbb0e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProvider.java
@@ -0,0 +1,57 @@
+/*
+ * 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.device;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of a device information provider.
+ */
+public interface DeviceProvider extends Provider {
+
+ // TODO: consider how dirty the triggerProbe gets; if it costs too much, let's drop it
+
+ /**
+ * Triggers an asynchronous probe of the specified device, intended to
+ * determine whether the device is present or not. An indirect result of this
+ * should be invocation of
+ * {@link org.onosproject.net.device.DeviceProviderService#deviceConnected} )} or
+ * {@link org.onosproject.net.device.DeviceProviderService#deviceDisconnected}
+ * at some later point in time.
+ *
+ * @param deviceId ID of device to be probed
+ */
+ void triggerProbe(DeviceId deviceId);
+
+ /**
+ * Notifies the provider of a mastership role change for the specified
+ * device as decided by the core.
+ *
+ * @param deviceId device identifier
+ * @param newRole newly determined mastership role
+ */
+ void roleChanged(DeviceId deviceId, MastershipRole newRole);
+
+ /**
+ * Checks the reachability (connectivity) of a device from this provider.
+ *
+ * @param deviceId device identifier
+ * @return true if reachable, false otherwise
+ */
+ boolean isReachable(DeviceId deviceId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderRegistry.java
new file mode 100644
index 00000000..a7ab7e36
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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.device;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction of a device provider registry.
+ */
+public interface DeviceProviderRegistry
+ extends ProviderRegistry<DeviceProvider, DeviceProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderService.java
new file mode 100644
index 00000000..e266df09
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceProviderService.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.provider.ProviderService;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Service through which device providers can inject device information into
+ * the core.
+ */
+public interface DeviceProviderService extends ProviderService<DeviceProvider> {
+
+ // TODO: define suspend and remove actions on the mezzanine administrative API
+
+ /**
+ * Signals the core that a device has connected or has been detected somehow.
+ *
+ * @param deviceId device identifier
+ * @param deviceDescription information about network device
+ */
+ void deviceConnected(DeviceId deviceId, DeviceDescription deviceDescription);
+
+ /**
+ * Signals the core that a device has disconnected or is no longer reachable.
+ *
+ * @param deviceId identity of the device to be removed
+ */
+ void deviceDisconnected(DeviceId deviceId);
+
+ /**
+ * Sends information about all ports of a device. It is up to the core to
+ * determine what has changed.
+ *
+ * @param deviceId identity of the device
+ * @param portDescriptions list of device ports
+ */
+ void updatePorts(DeviceId deviceId, List<PortDescription> portDescriptions);
+
+ /**
+ * Used to notify the core about port status change of a single port.
+ *
+ * @param deviceId identity of the device
+ * @param portDescription description of the port that changed
+ */
+ void portStatusChanged(DeviceId deviceId, PortDescription portDescription);
+
+ /**
+ * Notifies the core about the result of a RoleRequest sent to a device.
+ *
+ * @param deviceId identity of the device
+ * @param requested mastership role that was requested by the node
+ * @param response mastership role the switch accepted
+ */
+ void receivedRoleReply(DeviceId deviceId, MastershipRole requested, MastershipRole response);
+
+ /**
+ * Sends statistics about all ports of a device.
+ *
+ * @param deviceId identity of the device
+ * @param portStatistics list of device port statistics
+ */
+ void updatePortStatistics(DeviceId deviceId, Collection<PortStatistics> portStatistics);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
new file mode 100644
index 00000000..f4671fb4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import org.onosproject.event.ListenerService;
+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 java.util.List;
+
+/**
+ * Service for interacting with the inventory of infrastructure devices.
+ */
+public interface DeviceService
+ extends ListenerService<DeviceEvent, DeviceListener> {
+
+ /**
+ * Returns the number of infrastructure devices known to the system.
+ *
+ * @return number of infrastructure devices
+ */
+ int getDeviceCount();
+
+ /**
+ * Returns a collection of the currently known infrastructure
+ * devices.
+ *
+ * @return collection of devices
+ */
+ Iterable<Device> getDevices();
+
+ /**
+ * Returns a collection of the currently known infrastructure
+ * devices by device type.
+ *
+ * @param type device type
+ * @return collection of devices
+ */
+ Iterable<Device> getDevices(Device.Type type);
+
+ /**
+ * Returns an iterable collection of all devices
+ * currently available to the system.
+ *
+ * @return device collection
+ */
+ Iterable<Device> getAvailableDevices();
+
+ /**
+ * Returns an iterable collection of all devices currently available to the system by device type.
+ *
+ * @param type device type
+ * @return device collection
+ */
+ Iterable<Device> getAvailableDevices(Device.Type type);
+
+ /**
+ * Returns the device with the specified identifier.
+ *
+ * @param deviceId device identifier
+ * @return device or null if one with the given identifier is not known
+ */
+ Device getDevice(DeviceId deviceId);
+
+ /**
+ * Returns the current mastership role for the specified device.
+ *
+ * @param deviceId device identifier
+ * @return designated mastership role
+ */
+ //XXX do we want this method here when MastershipService already does?
+ MastershipRole getRole(DeviceId deviceId);
+
+
+ /**
+ * Returns the list of ports associated with the device.
+ *
+ * @param deviceId device identifier
+ * @return list of ports
+ */
+ List<Port> getPorts(DeviceId deviceId);
+
+ /**
+ * Returns the list of port statistics associated with the device.
+ *
+ * @param deviceId device identifier
+ * @return list of port statistics
+ */
+ List<PortStatistics> getPortStatistics(DeviceId deviceId);
+
+ /**
+ * Returns the list of port delta statistics associated with the device.
+ *
+ * @param deviceId device identifier
+ * @return list of port statistics
+ */
+ List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId);
+
+ /**
+ * Returns the port with the specified number and hosted by the given device.
+ *
+ * @param deviceId device identifier
+ * @param portNumber port number
+ * @return device port
+ */
+ Port getPort(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Indicates whether or not the device is presently online and available.
+ *
+ * @param deviceId device identifier
+ * @return true if the device is available
+ */
+ boolean isAvailable(DeviceId deviceId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStore.java
new file mode 100644
index 00000000..851b9709
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStore.java
@@ -0,0 +1,170 @@
+/*
+ * 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;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Store;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Manages inventory of infrastructure devices; not intended for direct use.
+ */
+public interface DeviceStore extends Store<DeviceEvent, DeviceStoreDelegate> {
+
+ /**
+ * Returns the number of devices known to the system.
+ *
+ * @return number of devices
+ */
+ int getDeviceCount();
+
+ /**
+ * Returns an iterable collection of all devices known to the system.
+ *
+ * @return device collection
+ */
+ Iterable<Device> getDevices();
+
+ /**
+ * Returns an iterable collection of all devices currently available to the system.
+ *
+ * @return device collection
+ */
+ Iterable<Device> getAvailableDevices();
+
+
+
+ /**
+ * Returns the device with the specified identifier.
+ *
+ * @param deviceId device identifier
+ * @return device
+ */
+ Device getDevice(DeviceId deviceId);
+
+ /**
+ * Creates a new infrastructure device, or updates an existing one using
+ * the supplied device description.
+ *
+ * @param providerId provider identifier
+ * @param deviceId device identifier
+ * @param deviceDescription device description
+ * @return ready to send event describing what occurred; null if no change
+ */
+ DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
+ DeviceDescription deviceDescription);
+
+ // TODO: We may need to enforce that ancillary cannot interfere this state
+ /**
+ * Removes the specified infrastructure device.
+ *
+ * @param deviceId device identifier
+ * @return ready to send event describing what occurred; null if no change
+ */
+ DeviceEvent markOffline(DeviceId deviceId);
+
+ /**
+ * Updates the ports of the specified infrastructure device using the given
+ * list of port descriptions. The list is assumed to be comprehensive.
+ *
+ * @param providerId provider identifier
+ * @param deviceId device identifier
+ * @param portDescriptions list of port descriptions
+ * @return ready to send events describing what occurred; empty list if no change
+ */
+ List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
+ List<PortDescription> portDescriptions);
+
+ /**
+ * Updates the port status of the specified infrastructure device using the
+ * given port description.
+ *
+ * @param providerId provider identifier
+ * @param deviceId device identifier
+ * @param portDescription port description
+ * @return ready to send event describing what occurred; null if no change
+ */
+ DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
+ PortDescription portDescription);
+
+ /**
+ * Returns the list of ports that belong to the specified device.
+ *
+ * @param deviceId device identifier
+ * @return list of device ports
+ */
+ List<Port> getPorts(DeviceId deviceId);
+
+ /**
+ * Updates the port statistics of the specified device using the give port
+ * statistics.
+ *
+ * @param providerId provider identifier
+ * @param deviceId device identifier
+ * @param portStats list of port statistics
+ * @return ready to send event describing what occurred;
+ */
+ DeviceEvent updatePortStatistics(ProviderId providerId, DeviceId deviceId,
+ Collection<PortStatistics> portStats);
+
+ /**
+ * Returns the list of port statistics of the specified device.
+ *
+ * @param deviceId device identifier
+ * @return list of port statistics of all ports of the device
+ */
+ List<PortStatistics> getPortStatistics(DeviceId deviceId);
+
+ /**
+ * Returns the list of delta port statistics of the specified device.
+ *
+ * @param deviceId device identifier
+ * @return list of delta port statistics of all ports of the device
+ */
+ List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId);
+
+ /**
+ * Returns the specified device port.
+ *
+ * @param deviceId device identifier
+ * @param portNumber port number
+ * @return device port
+ */
+ Port getPort(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Indicates whether the specified device is available/online.
+ *
+ * @param deviceId device identifier
+ * @return true if device is available
+ */
+ boolean isAvailable(DeviceId deviceId);
+
+ /**
+ * Administratively removes the specified device from the store.
+ *
+ * @param deviceId device to be removed
+ * @return null if no such device, or was forwarded to remove master
+ */
+ DeviceEvent removeDevice(DeviceId deviceId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStoreDelegate.java
new file mode 100644
index 00000000..1a4fc67d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/DeviceStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.device;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Infrastructure device store delegate abstraction.
+ */
+public interface DeviceStoreDelegate extends StoreDelegate<DeviceEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OchPortDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OchPortDescription.java
new file mode 100644
index 00000000..c3a7f415
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OchPortDescription.java
@@ -0,0 +1,111 @@
+/*
+ * 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.device;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of immutable OCh port description.
+ */
+public class OchPortDescription extends DefaultPortDescription {
+
+ private final OduSignalType signalType;
+ private final boolean isTunable;
+ private final OchSignal lambda;
+
+ /**
+ * Creates OCH port description based on the supplied information.
+ *
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param signalType ODU signal type
+ * @param isTunable tunable wavelength capability
+ * @param lambda OCh signal
+ * @param annotations optional key/value annotations map
+ */
+ public OchPortDescription(PortNumber number, boolean isEnabled, OduSignalType signalType,
+ boolean isTunable, OchSignal lambda, SparseAnnotations... annotations) {
+ super(number, isEnabled, Port.Type.OCH, 0, annotations);
+ this.signalType = signalType;
+ this.isTunable = isTunable;
+ this.lambda = checkNotNull(lambda);
+ }
+
+ /**
+ * Creates OCH port description based on the supplied information.
+ *
+ * @param base PortDescription to get basic information from
+ * @param signalType ODU signal type
+ * @param isTunable tunable wavelength capability
+ * @param lambda OCh signal
+ * @param annotations optional key/value annotations map
+ */
+ public OchPortDescription(PortDescription base, OduSignalType signalType, boolean isTunable,
+ OchSignal lambda, SparseAnnotations annotations) {
+ super(base, annotations);
+ this.signalType = signalType;
+ this.isTunable = isTunable;
+ this.lambda = checkNotNull(lambda);
+ }
+
+ /**
+ * Returns ODU signal type.
+ *
+ * @return ODU signal type
+ */
+ public OduSignalType signalType() {
+ return signalType;
+ }
+
+ /**
+ * Returns true if port is wavelength tunable.
+ *
+ * @return tunable wavelength capability
+ */
+ public boolean isTunable() {
+ return isTunable;
+ }
+
+ /**
+ * Returns OCh signal.
+ *
+ * @return OCh signal
+ */
+ public OchSignal lambda() {
+ return lambda;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("number", portNumber())
+ .add("isEnabled", isEnabled())
+ .add("type", type())
+ .add("signalType", signalType)
+ .add("isTunable", isTunable)
+ .add("lambda", lambda)
+ .add("annotations", annotations())
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OduCltPortDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OduCltPortDescription.java
new file mode 100644
index 00000000..eee7de2d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OduCltPortDescription.java
@@ -0,0 +1,77 @@
+/*
+ * 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.device;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.OduCltPort;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+
+/**
+ * Default implementation of immutable ODU client port description.
+ */
+public class OduCltPortDescription extends DefaultPortDescription {
+
+ private final OduCltPort.SignalType signalType;
+
+ /**
+ * Creates ODU client port description based on the supplied information.
+ *
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param signalType ODU client signal type
+ * @param annotations optional key/value annotations map
+ */
+ public OduCltPortDescription(PortNumber number, boolean isEnabled, OduCltPort.SignalType signalType,
+ SparseAnnotations... annotations) {
+ super(number, isEnabled, Port.Type.ODUCLT, 0, annotations);
+ this.signalType = signalType;
+ }
+
+ /**
+ * Creates ODU client port description based on the supplied information.
+ *
+ * @param base PortDescription to get basic information from
+ * @param signalType ODU client signal type
+ * @param annotations optional key/value annotations map
+ */
+ public OduCltPortDescription(PortDescription base, OduCltPort.SignalType signalType,
+ SparseAnnotations annotations) {
+ super(base, annotations);
+ this.signalType = signalType;
+ }
+
+ /**
+ * Returns ODU client signal type.
+ *
+ * @return ODU client signal type
+ */
+ public OduCltPort.SignalType signalType() {
+ return signalType;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("number", portNumber())
+ .add("isEnabled", isEnabled())
+ .add("type", type())
+ .add("signalType", signalType)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OmsPortDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OmsPortDescription.java
new file mode 100644
index 00000000..131314a3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/OmsPortDescription.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.device;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.util.Frequency;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+
+/**
+ * Default implementation of immutable OMS port description.
+ */
+public class OmsPortDescription extends DefaultPortDescription {
+
+ private final Frequency minFrequency;
+ private final Frequency maxFrequency;
+ private final Frequency grid;
+
+ /**
+ * Creates OMS port description based on the supplied information.
+ *
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param minFrequency minimum frequency
+ * @param maxFrequency maximum frequency
+ * @param grid grid spacing frequency
+ * @param annotations optional key/value annotations map
+ */
+ public OmsPortDescription(PortNumber number, boolean isEnabled, Frequency minFrequency, Frequency maxFrequency,
+ Frequency grid, SparseAnnotations... annotations) {
+ super(number, isEnabled, Port.Type.OMS, 0, annotations);
+ this.minFrequency = minFrequency;
+ this.maxFrequency = maxFrequency;
+ this.grid = grid;
+ }
+
+ /**
+ * Creates OMS port description based on the supplied information.
+ *
+ * @param base PortDescription to get basic information from
+ * @param minFrequency minimum frequency
+ * @param maxFrequency maximum frequency
+ * @param grid grid spacing frequency
+ * @param annotations optional key/value annotations map
+ */
+ public OmsPortDescription(PortDescription base, Frequency minFrequency, Frequency maxFrequency,
+ Frequency grid, SparseAnnotations annotations) {
+ super(base, annotations);
+ this.minFrequency = minFrequency;
+ this.maxFrequency = maxFrequency;
+ this.grid = grid;
+ }
+
+ /**
+ * Returns minimum frequency.
+ *
+ * @return minimum frequency
+ */
+ public Frequency minFrequency() {
+ return minFrequency;
+ }
+
+ /**
+ * Returns maximum frequency.
+ *
+ * @return maximum frequency
+ */
+ public Frequency maxFrequency() {
+ return maxFrequency;
+ }
+
+ /**
+ * Returns grid spacing frequency.
+ *
+ * @return grid spacing frequency
+ */
+ public Frequency grid() {
+ return grid;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("number", portNumber())
+ .add("isEnabled", isEnabled())
+ .add("type", type())
+ .add("minFrequency", minFrequency)
+ .add("maxFrequency", maxFrequency)
+ .add("grid", grid)
+ .add("annotations", annotations())
+ .toString();
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortDescription.java
new file mode 100644
index 00000000..3ed3efce
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortDescription.java
@@ -0,0 +1,56 @@
+/*
+ * 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.device;
+
+import org.onosproject.net.Description;
+import org.onosproject.net.PortNumber;
+
+import static org.onosproject.net.Port.Type;
+
+/**
+ * Information about a port.
+ */
+public interface PortDescription extends Description {
+
+ /**
+ * Returns the port number.
+ *
+ * @return port number
+ */
+ PortNumber portNumber();
+
+ /**
+ * Indicates whether or not the port is up and active.
+ *
+ * @return true if the port is active and has carrier signal
+ */
+ boolean isEnabled();
+
+ /**
+ * Returns the port type.
+ *
+ * @return port type
+ */
+ Type type();
+
+ /**
+ * Returns the current port speed in Mbps.
+ *
+ * @return current port speed
+ */
+ long portSpeed();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java
new file mode 100644
index 00000000..201bd7b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java
@@ -0,0 +1,100 @@
+/*
+ * 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.device;
+
+/**
+ * Statistics of a port.
+ */
+public interface PortStatistics {
+
+ /**
+ * Returns the port number.
+ *
+ * @return port number
+ */
+ int port();
+
+ /**
+ * Returns the number of packets received.
+ *
+ * @return the number of packets received
+ */
+ long packetsReceived();
+
+ /**
+ * Returns the number of packets sent.
+ *
+ * @return the number of packets sent
+ */
+ long packetsSent();
+
+ /**
+ * Returns the bytes received.
+ *
+ * @return the bytes received
+ */
+ long bytesReceived();
+
+ /**
+ * Returns the bytes sent.
+ *
+ * @return the bytes sent
+ */
+ long bytesSent();
+
+ /**
+ * Returns the number of packets dropped by RX.
+ *
+ * @return the number of packets dropped by RX
+ */
+ long packetsRxDropped();
+
+ /**
+ * Returns the number of packets dropped by TX.
+ *
+ * @return the number of packets dropped by TX
+ */
+ long packetsTxDropped();
+
+ /**
+ * Returns the number of transmit errors.
+ *
+ * @return the number of transmit errors
+ */
+ long packetsRxErrors();
+
+ /**
+ * Returns the number of receive errors.
+ *
+ * @return the number of receive error
+ */
+ long packetsTxErrors();
+
+ /**
+ * Returns the time port has been alive in seconds.
+ *
+ * @return the time port has been alive in seconds
+ */
+ long durationSec();
+
+ /**
+ * Returns the time port has been alive in nano seconds.
+ *
+ * @return the time port has been alive in nano seconds
+ */
+ long durationNano();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/package-info.java
new file mode 100644
index 00000000..4ee64dcf
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/device/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.
+ */
+
+/**
+ * Infrastructure device model &amp; related services API definitions.
+ */
+package org.onosproject.net.device;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractBehaviour.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractBehaviour.java
new file mode 100644
index 00000000..784e6c55
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractBehaviour.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of device driver behaviour.
+ */
+public class AbstractBehaviour implements Behaviour {
+
+ private DriverData data;
+
+ @Override
+ public DriverData data() {
+ return data;
+ }
+
+ @Override
+ public void setData(DriverData data) {
+ checkState(this.data == null, "Driver data already set");
+ this.data = data;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractHandlerBehaviour.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractHandlerBehaviour.java
new file mode 100644
index 00000000..66b21ffe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/AbstractHandlerBehaviour.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of device driver handler behaviour.
+ */
+public class AbstractHandlerBehaviour
+ extends AbstractBehaviour implements HandlerBehaviour {
+
+ private DriverHandler handler;
+
+ @Override
+ public DriverHandler handler() {
+ return handler;
+ }
+
+ @Override
+ public void setHandler(DriverHandler handler) {
+ checkState(this.handler == null, "Driver handler already set");
+ this.handler = handler;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Behaviour.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Behaviour.java
new file mode 100644
index 00000000..6e28aa86
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Behaviour.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Representation of a facet of device behaviour that can be used to talk about
+ * a device (in context of {@link DriverData}) or to a device (in context of
+ * {@link DriverHandler}).
+ */
+public interface Behaviour {
+
+ /**
+ * Returns the driver data context.
+ *
+ * @return driver data
+ */
+ DriverData data();
+
+ /**
+ * Sets the driver data context on this this behaviour should operate.
+ *
+ * @param data driver data
+ */
+ void setData(DriverData data);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriver.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriver.java
new file mode 100644
index 00000000..b7a9f2b7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriver.java
@@ -0,0 +1,214 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableMap.copyOf;
+
+/**
+ * Default implementation of extensible driver.
+ */
+public class DefaultDriver implements Driver {
+
+ private final String name;
+ private final Driver parent;
+
+ private final String manufacturer;
+ private final String hwVersion;
+ private final String swVersion;
+
+ private final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours;
+ private final Map<String, String> properties;
+
+ /**
+ * Creates a driver with the specified name.
+ *
+ * @param name driver name
+ * @param parent optional parent driver
+ * @param manufacturer device manufacturer
+ * @param hwVersion device hardware version
+ * @param swVersion device software version
+ * @param behaviours device behaviour classes
+ * @param properties properties for configuration of device behaviour classes
+ */
+ public DefaultDriver(String name, Driver parent, String manufacturer,
+ String hwVersion, String swVersion,
+ Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours,
+ Map<String, String> properties) {
+ this.name = checkNotNull(name, "Name cannot be null");
+ this.parent = parent;
+ this.manufacturer = checkNotNull(manufacturer, "Manufacturer cannot be null");
+ this.hwVersion = checkNotNull(hwVersion, "HW version cannot be null");
+ this.swVersion = checkNotNull(swVersion, "SW version cannot be null");
+ this.behaviours = copyOf(checkNotNull(behaviours, "Behaviours cannot be null"));
+ this.properties = copyOf(checkNotNull(properties, "Properties cannot be null"));
+ }
+
+ @Override
+ public Driver merge(Driver other) {
+ checkArgument(parent == null || Objects.equals(parent, other.parent()),
+ "Parent drivers are not the same");
+
+ // Merge the behaviours.
+ Map<Class<? extends Behaviour>, Class<? extends Behaviour>>
+ behaviours = Maps.newHashMap();
+ behaviours.putAll(this.behaviours);
+ other.behaviours().forEach(b -> behaviours.put(b, other.implementation(b)));
+
+ // Merge the properties.
+ ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();
+ properties.putAll(this.properties).putAll(other.properties());
+
+ return new DefaultDriver(name, other.parent(), manufacturer, hwVersion, swVersion,
+ ImmutableMap.copyOf(behaviours), properties.build());
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String manufacturer() {
+ return manufacturer;
+ }
+
+ @Override
+ public String hwVersion() {
+ return hwVersion;
+ }
+
+ @Override
+ public String swVersion() {
+ return swVersion;
+ }
+
+ @Override
+ public Driver parent() {
+ return parent;
+ }
+
+ @Override
+ public Set<Class<? extends Behaviour>> behaviours() {
+ return behaviours.keySet();
+ }
+
+ @Override
+ public Class<? extends Behaviour> implementation(Class<? extends Behaviour> behaviour) {
+ return behaviours.get(behaviour);
+ }
+
+ @Override
+ public boolean hasBehaviour(Class<? extends Behaviour> behaviourClass) {
+ return behaviours.containsKey(behaviourClass) ||
+ (parent != null && parent.hasBehaviour(behaviourClass));
+ }
+
+ @Override
+ public <T extends Behaviour> T createBehaviour(DriverData data,
+ Class<T> behaviourClass) {
+ T behaviour = createBehaviour(data, null, behaviourClass);
+ if (behaviour != null) {
+ return behaviour;
+ } else if (parent != null) {
+ return parent.createBehaviour(data, behaviourClass);
+ }
+ throw new IllegalArgumentException(behaviourClass.getName() + " not supported");
+ }
+
+ @Override
+ public <T extends Behaviour> T createBehaviour(DriverHandler handler,
+ Class<T> behaviourClass) {
+ T behaviour = createBehaviour(handler.data(), handler, behaviourClass);
+ if (behaviour != null) {
+ return behaviour;
+ } else if (parent != null) {
+ return parent.createBehaviour(handler, behaviourClass);
+ }
+ throw new IllegalArgumentException(behaviourClass.getName() + " not supported");
+ }
+
+ // Creates an instance of behaviour primed with the specified driver data.
+ private <T extends Behaviour> T createBehaviour(DriverData data, DriverHandler handler,
+ Class<T> behaviourClass) {
+ //checkArgument(handler != null || !HandlerBehaviour.class.isAssignableFrom(behaviourClass),
+ // "{} is applicable only to handler context", behaviourClass.getName());
+
+ // Locate the implementation of the requested behaviour.
+ Class<? extends Behaviour> implementation = behaviours.get(behaviourClass);
+ if (implementation != null) {
+ // Create an instance of the behaviour and apply data as its context.
+ T behaviour = createBehaviour(behaviourClass, implementation);
+ behaviour.setData(data);
+
+ // If this is a handler behaviour, also apply handler as its context.
+ if (handler != null) {
+ ((HandlerBehaviour) behaviour).setHandler(handler);
+ }
+ return behaviour;
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends Behaviour> T createBehaviour(Class<T> behaviourClass,
+ Class<? extends Behaviour> implementation) {
+ try {
+ return (T) implementation.newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ // TODO: add a specific unchecked exception
+ throw new IllegalArgumentException("Unable to create behaviour", e);
+ }
+ }
+
+ @Override
+ public Set<String> keys() {
+ return properties.keySet();
+ }
+
+ @Override
+ public String value(String key) {
+ return properties.get(key);
+ }
+
+ @Override
+ public Map<String, String> properties() {
+ return properties;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("name", name)
+ .add("parent", parent)
+ .add("manufacturer", manufacturer)
+ .add("hwVersion", hwVersion)
+ .add("swVersion", swVersion)
+ .add("behaviours", behaviours)
+ .add("properties", properties)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverData.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverData.java
new file mode 100644
index 00000000..76d7932d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverData.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MutableAnnotations;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of driver data descriptor.
+ */
+public class DefaultDriverData implements DriverData {
+
+ private final Driver driver;
+ private final DeviceId deviceId;
+ private final Map<String, String> properties;
+
+ /**
+ * Creates new driver data.
+ *
+ * @param driver parent driver type
+ * @param deviceId device identifier
+ */
+ public DefaultDriverData(Driver driver, DeviceId deviceId) {
+ this.driver = driver;
+ this.deviceId = deviceId;
+ this.properties = new HashMap<>();
+ }
+
+ @Override
+ public Driver driver() {
+ return driver;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public <T extends Behaviour> T behaviour(Class<T> behaviourClass) {
+ return driver.createBehaviour(this, behaviourClass);
+ }
+
+ @Override
+ public MutableAnnotations set(String key, String value) {
+ properties.put(key, value);
+ return this;
+ }
+
+ @Override
+ public MutableAnnotations clear(String... keys) {
+ if (keys.length == 0) {
+ properties.clear();
+ } else {
+ for (String key : keys) {
+ properties.remove(key);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public Set<String> keys() {
+ return ImmutableSet.copyOf(properties.keySet());
+ }
+
+ @Override
+ public String value(String key) {
+ return properties.get(key);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("type", driver)
+ .add("properties", properties)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverHandler.java
new file mode 100644
index 00000000..28fdb2f3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of driver handler.
+ */
+public class DefaultDriverHandler implements DriverHandler {
+
+ private final DefaultDriverData data;
+
+ // Reference to service directory to provide run-time context.
+ protected static ServiceDirectory serviceDirectory = new DefaultServiceDirectory();
+
+ /**
+ * Creates new driver handler with the attached driver data.
+ *
+ * @param data driver data to attach
+ */
+ public DefaultDriverHandler(DefaultDriverData data) {
+ this.data = data;
+ }
+
+ @Override
+ public Driver driver() {
+ return data.driver();
+ }
+
+ @Override
+ public DriverData data() {
+ return data;
+ }
+
+ @Override
+ public <T extends Behaviour> T behaviour(Class<T> behaviourClass) {
+ return data.driver().createBehaviour(this, behaviourClass);
+ }
+
+ @Override
+ public <T> T get(Class<T> serviceClass) {
+ return serviceDirectory.get(serviceClass);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("data", data).toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProvider.java
new file mode 100644
index 00000000..b2b5281c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProvider.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.driver;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default driver provider implementation.
+ */
+public class DefaultDriverProvider implements DriverProvider {
+
+ protected final Map<String, Driver> drivers = Maps.newConcurrentMap();
+
+ @Override
+ public Set<Driver> getDrivers() {
+ return ImmutableSet.copyOf(drivers.values());
+ }
+
+ /**
+ * Adds the specified drivers to the provider.
+ *
+ * @param drivers drivers to be added
+ */
+ public void addDrivers(Set<Driver> drivers) {
+ drivers.forEach(this::addDriver);
+ }
+
+ /**
+ * Adds the specified driver to the provider.
+ *
+ * @param driver driver to be provided
+ */
+ public void addDriver(Driver driver) {
+ Driver ddc = drivers.get(driver.name());
+ if (ddc == null) {
+ // If we don't have the driver yet, just use the new one.
+ drivers.put(driver.name(), driver);
+ } else {
+ // Otherwise merge the existing driver with the new one and rebind.
+ drivers.put(driver.name(), ddc.merge(driver));
+ }
+ }
+
+ /**
+ * Removes the specified drivers from the provider.
+ *
+ * @param drivers drivers to be removed
+ */
+ public void removeDrivers(Set<Driver> drivers) {
+ drivers.forEach(this::removeDriver);
+ }
+
+ /**
+ * Removes the specified driver from the provider.
+ *
+ * @param driver driver to be removed
+ */
+ public void removeDriver(Driver driver) {
+ // TODO: make selective if possible
+ drivers.remove(driver.name());
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("drivers", drivers).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProviderService.java
new file mode 100644
index 00000000..153c7c56
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DefaultDriverProviderService.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * Service representing availability of default drivers.
+ */
+public interface DefaultDriverProviderService {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Driver.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Driver.java
new file mode 100644
index 00000000..50611b14
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/Driver.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import org.onosproject.net.Annotations;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Representation of a specific family of device drivers. Behaviour configuration
+ * data is stored using {@link org.onosproject.net.Annotations}.
+ */
+public interface Driver extends Annotations {
+
+ /**
+ * Returns the driver name. This is expected to be a reverse-DNS,
+ * Java package-like name.
+ *
+ * @return driver name
+ */
+ String name();
+
+ /**
+ * Returns the parent driver from which this driver inherits behaviours
+ * and properties.
+ *
+ * @return parent driver; null if driver has no parent
+ */
+ Driver parent();
+
+ /**
+ * Returns the device manufacturer name.
+ *
+ * @return manufacturer name
+ */
+ String manufacturer();
+
+ /**
+ * Returns the device hardware version.
+ *
+ * @return hardware version
+ */
+ String hwVersion();
+
+ /**
+ * Returns the device software version.
+ *
+ * @return software version
+ */
+ String swVersion();
+
+ /**
+ * Returns the set of behaviours supported by this driver.
+ * It reflects behaviours of only this driver and not its parent.
+ *
+ * @return set of device driver behaviours
+ */
+ Set<Class<? extends Behaviour>> behaviours();
+
+ /**
+ * Returns the implementation class for the specified behaviour.
+ * It reflects behaviours of only this driver and not its parent.
+ *
+ * @param behaviour behaviour interface
+ * @return implementation class
+ */
+ Class<? extends Behaviour> implementation(Class<? extends Behaviour> behaviour);
+
+ /**
+ * Indicates whether or not the driver, or any of its parents, support
+ * the specified class of behaviour. It
+ *
+ * @param behaviourClass behaviour class
+ * @return true if behaviour is supported
+ */
+ boolean hasBehaviour(Class<? extends Behaviour> behaviourClass);
+
+ /**
+ * Creates an instance of behaviour primed with the specified driver data.
+ * If the current driver does not support the specified behaviour and the
+ * driver has parent, the request is delegated to the parent driver.
+ *
+ * @param data driver data context
+ * @param behaviourClass driver behaviour class
+ * @param <T> type of behaviour
+ * @return behaviour instance
+ */
+ <T extends Behaviour> T createBehaviour(DriverData data, Class<T> behaviourClass);
+
+ /**
+ * Creates an instance of behaviour primed with the specified driver data.
+ * If the current driver does not support the specified behaviour and the
+ * driver has parent, the request is delegated to the parent driver.
+ *
+ * @param handler driver handler context
+ * @param behaviourClass driver behaviour class
+ * @param <T> type of behaviour
+ * @return behaviour instance
+ */
+ <T extends Behaviour> T createBehaviour(DriverHandler handler, Class<T> behaviourClass);
+
+ /**
+ * Returns the set of annotations as map of key/value properties.
+ *
+ * @return map of properties
+ */
+ Map<String, String> properties();
+
+ /**
+ * Merges the specified driver behaviours and properties into this one,
+ * giving preference to the other driver when dealing with conflicts.
+ *
+ * @param other other driver
+ * @return merged driver
+ */
+ Driver merge(Driver other);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverAdminService.java
new file mode 100644
index 00000000..e7fa1385
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverAdminService.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.driver;
+
+import java.util.Set;
+
+/**
+ * Service for managing drivers and driver behaviour implementations.
+ */
+public interface DriverAdminService extends DriverService {
+
+ /**
+ * Returns the set of driver providers currently registered.
+ *
+ * @return registered driver providers
+ */
+ Set<DriverProvider> getProviders();
+
+ /**
+ * Registers the specified driver provider.
+ *
+ * @param provider driver provider to register
+ */
+ void registerProvider(DriverProvider provider);
+
+ /**
+ * Unregisters the specified driver provider.
+ *
+ * @param provider driver provider to unregister
+ */
+ void unregisterProvider(DriverProvider provider);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverConnect.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverConnect.java
new file mode 100644
index 00000000..1d510913
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverConnect.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of handler behaviour used to set-up and tear-down driver
+ * connection with a device.
+ */
+public interface DriverConnect extends HandlerBehaviour {
+
+ /**
+ * Connects to the device.
+ *
+ * @param credentials optional login credentials in string form
+ */
+ void connect(String... credentials);
+
+ /**
+ * Disconnects from the device.
+ */
+ void disconnect();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverData.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverData.java
new file mode 100644
index 00000000..1d66ea9a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverData.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MutableAnnotations;
+
+/**
+ * Container for data about a device. Data is stored using
+ * {@link org.onosproject.net.MutableAnnotations}.
+ */
+public interface DriverData extends MutableAnnotations {
+
+ /**
+ * Returns the parent device driver.
+ *
+ * @return device driver
+ */
+ Driver driver();
+
+ /**
+ * Returns the device identifier.
+ *
+ * @return device identifier
+ */
+ DeviceId deviceId();
+
+ /**
+ * Returns the specified facet of behaviour to access the device data.
+ *
+ * @param behaviourClass behaviour class
+ * @param <T> type of behaviour
+ * @return requested behaviour or null if not supported
+ */
+ <T extends Behaviour> T behaviour(Class<T> behaviourClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverHandler.java
new file mode 100644
index 00000000..202708ba
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * Representation of context for interacting with a device.
+ */
+public interface DriverHandler {
+
+ /**
+ * Returns the parent device driver.
+ *
+ * @return device driver
+ */
+ Driver driver();
+
+ /**
+ * Returns the device driver data.
+ *
+ * @return device driver data
+ */
+ DriverData data();
+
+ /**
+ * Returns the specified facet of behaviour to interact with the device.
+ *
+ * @param behaviourClass behaviour class
+ * @param <T> type of behaviour
+ * @return behaviour
+ */
+ <T extends Behaviour> T behaviour(Class<T> behaviourClass);
+
+ /**
+ * Returns the reference to the implementation of the specified service.
+ * Provides access to run-time context.
+ *
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return service implementation
+ * @throws org.onlab.osgi.ServiceNotFoundException if service is unavailable
+ */
+ <T> T get(Class<T> serviceClass);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverProvider.java
new file mode 100644
index 00000000..354d93e9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+/**
+ * Represents entity capable of providing device drivers and their
+ * behaviours.
+ */
+public interface DriverProvider {
+
+ /**
+ * Returns the set of driver types and behaviour implementations to be
+ * made available by this provider.
+ *
+ * @return set of driver types and their behaviours
+ */
+ Set<Driver> getDrivers();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverResolver.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverResolver.java
new file mode 100644
index 00000000..094c710f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverResolver.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Entity capable of resolving a driver using its name.
+ */
+public interface DriverResolver {
+
+ /**
+ * Returns the specified driver.
+ *
+ * @param driverName driver name
+ * @return driver
+ * @throws org.onlab.util.ItemNotFoundException if driver with the given
+ * name is not found
+ */
+ Driver getDriver(String driverName);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverService.java
new file mode 100644
index 00000000..1a74255b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/DriverService.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Set;
+
+/**
+ * Service for obtaining drivers and driver behaviour implementations.
+ */
+public interface DriverService extends DriverResolver {
+
+ /**
+ * Returns the overall set of drivers being provided.
+ *
+ * @return provided drivers
+ */
+ Set<Driver> getDrivers();
+
+ /**
+ * Returns the set of drivers which support the specified behaviour.
+ *
+ * @param withBehaviour behaviour class to query by
+ * @return provided drivers
+ */
+ Set<Driver> getDrivers(Class<? extends Behaviour> withBehaviour);
+
+ /**
+ * Returns the driver that matches the specified primordial device
+ * discovery information.
+ *
+ * @param mfr device manufacturer
+ * @param hw device hardware name/version
+ * @param sw device software version
+ * @return driver or null of no matching one is found
+ */
+ Driver getDriver(String mfr, String hw, String sw);
+
+ /**
+ * Returns the driver for the specified device. If the device carries
+ * {@code driver} annotation, its value is used to look-up the driver.
+ * Otherwise, the device manufacturer, hardware and software version
+ * attributes are used to look-up the driver. First using their literal
+ * values and if no driver is found, using ERE matching against the
+ * driver manufacturer, hardware and software version fields.
+ *
+ * @param deviceId device identifier
+ * @return driver or null of no matching one is found
+ * @throws org.onlab.util.ItemNotFoundException if device or driver for it
+ * are not found
+ */
+ Driver getDriver(DeviceId deviceId);
+
+ /**
+ * Creates a new driver handler for interacting with the specified device.
+ * The driver is looked-up using the same semantics as
+ * {@link #getDriver(DeviceId)} method.
+ *
+ * @param deviceId device identifier
+ * @param credentials optional login credentials in string form
+ * @return driver handler
+ * @throws org.onlab.util.ItemNotFoundException if device or driver for it
+ * are not found
+ */
+ DriverHandler createHandler(DeviceId deviceId, String... credentials);
+
+ // TODO: Devise a mechanism for retaining DriverData for devices
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/HandlerBehaviour.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/HandlerBehaviour.java
new file mode 100644
index 00000000..b5771ac1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/HandlerBehaviour.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Representation of a facet of device behaviour that can be used to interact
+ * with a device (in context of {@link org.onosproject.net.driver.DriverHandler}).
+ */
+public interface HandlerBehaviour extends Behaviour {
+
+ /**
+ * Returns the driver handler context on which this behaviour operates.
+ *
+ * @return driver handler context
+ */
+ DriverHandler handler();
+
+ /**
+ * Sets the driver handler context for this behaviour.
+ *
+ * @param handler driver handler
+ */
+ void setHandler(DriverHandler handler);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/XmlDriverLoader.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/XmlDriverLoader.java
new file mode 100644
index 00000000..fc5e04a1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/XmlDriverLoader.java
@@ -0,0 +1,176 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Utility capable of reading driver configuration XML resources and producing
+ * a device driver provider as a result.
+ * <p>
+ * The drivers stream structure is as follows:
+ * </p>
+ * <pre>
+ * &lt;drivers&gt;
+ * &lt;driver name=“...” [manufacturer="..." hwVersion="..." swVersion="..."]&gt;
+ * &lt;behaviour api="..." impl="..."/&gt;
+ * ...
+ * [&lt;property name=“key”&gt;value&lt;/key&gt;]
+ * ...
+ * &lt;/driver&gt;
+ * ...
+ * &lt;/drivers&gt;
+ * </pre>
+ */
+public class XmlDriverLoader {
+
+ private static final String DRIVERS = "drivers";
+ private static final String DRIVER = "driver";
+
+ private static final String BEHAVIOUR = "behaviour";
+ private static final String PROPERTY = "property";
+
+ private static final String NAME = "[@name]";
+ private static final String EXTENDS = "[@extends]";
+ private static final String MFG = "[@manufacturer]";
+ private static final String HW = "[@hwVersion]";
+ private static final String SW = "[@swVersion]";
+ private static final String API = "[@api]";
+ private static final String IMPL = "[@impl]";
+
+ private final ClassLoader classLoader;
+
+ private Map<String, Driver> drivers = Maps.newHashMap();
+
+ /**
+ * Creates a new driver loader capable of loading drivers from the supplied
+ * class loader.
+ *
+ * @param classLoader class loader to use
+ */
+ public XmlDriverLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Loads the specified drivers resource as an XML stream and parses it to
+ * produce a ready-to-register driver provider.
+ *
+ * @param driversStream stream containing the drivers definitions
+ * @param resolver driver resolver
+ * @return driver provider
+ * @throws java.io.IOException if issues are encountered reading the stream
+ * or parsing the driver definitions within
+ */
+ public DefaultDriverProvider loadDrivers(InputStream driversStream,
+ DriverResolver resolver) throws IOException {
+ try {
+ XMLConfiguration cfg = new XMLConfiguration();
+ cfg.setRootElementName(DRIVERS);
+ cfg.setAttributeSplittingDisabled(true);
+
+ cfg.load(driversStream);
+ return loadDrivers(cfg, resolver);
+ } catch (ConfigurationException e) {
+ throw new IOException("Unable to load drivers", e);
+ }
+ }
+
+ /**
+ * Loads a driver provider from the supplied hierarchical configuration.
+ *
+ * @param driversCfg hierarchical configuration containing the drivers definitions
+ * @param resolver driver resolver
+ * @return driver provider
+ */
+ public DefaultDriverProvider loadDrivers(HierarchicalConfiguration driversCfg,
+ DriverResolver resolver) {
+ DefaultDriverProvider provider = new DefaultDriverProvider();
+ for (HierarchicalConfiguration cfg : driversCfg.configurationsAt(DRIVER)) {
+ DefaultDriver driver = loadDriver(cfg, resolver);
+ drivers.put(driver.name(), driver);
+ provider.addDriver(driver);
+ }
+ drivers.clear();
+ return provider;
+ }
+
+ /**
+ * Loads a driver from the supplied hierarchical configuration.
+ *
+ * @param driverCfg hierarchical configuration containing the driver definition
+ * @param resolver driver resolver
+ * @return driver
+ */
+ public DefaultDriver loadDriver(HierarchicalConfiguration driverCfg,
+ DriverResolver resolver) {
+ String name = driverCfg.getString(NAME);
+ String parentName = driverCfg.getString(EXTENDS);
+ String manufacturer = driverCfg.getString(MFG, "");
+ String hwVersion = driverCfg.getString(HW, "");
+ String swVersion = driverCfg.getString(SW, "");
+
+ Driver parent = parentName != null ? resolve(parentName, resolver) : null;
+ return new DefaultDriver(name, parent, manufacturer, hwVersion, swVersion,
+ parseBehaviours(driverCfg),
+ parseProperties(driverCfg));
+ }
+
+ // Resolves the driver by name locally at first and then using the specified resolver.
+ private Driver resolve(String parentName, DriverResolver resolver) {
+ Driver driver = drivers.get(parentName);
+ return driver != null ? driver :
+ (resolver != null ? resolver.getDriver(parentName) : null);
+ }
+
+ // Parses the behaviours section.
+ private Map<Class<? extends Behaviour>, Class<? extends Behaviour>>
+ parseBehaviours(HierarchicalConfiguration driverCfg) {
+ ImmutableMap.Builder<Class<? extends Behaviour>,
+ Class<? extends Behaviour>> behaviours = ImmutableMap.builder();
+ for (HierarchicalConfiguration b : driverCfg.configurationsAt(BEHAVIOUR)) {
+ behaviours.put(getClass(b.getString(API)), getClass(b.getString(IMPL)));
+ }
+ return behaviours.build();
+ }
+
+ // Parses the properties section.
+ private Map<String, String> parseProperties(HierarchicalConfiguration driverCfg) {
+ ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();
+ for (HierarchicalConfiguration b : driverCfg.configurationsAt(PROPERTY)) {
+ properties.put(b.getString(NAME), (String) b.getRootNode().getValue());
+ }
+ return properties.build();
+ }
+
+ @SuppressWarnings("unchecked")
+ private Class<? extends Behaviour> getClass(String className) {
+ try {
+ return (Class<? extends Behaviour>) classLoader.loadClass(className);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Unable to load class " + className, e);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/package-info.java
new file mode 100644
index 00000000..fbc39a89
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/driver/package-info.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+/**
+ * Set of facilities to allow the platform to be extended with
+ * device specific behaviours and to allow modeling device behaviours while
+ * hiding details of specific device driver implementations.
+ * <p>
+ * {@link org.onosproject.net.driver.Driver} is a representation of a
+ * specific family of devices supports set of
+ * {@link org.onosproject.net.driver.Behaviour behaviour classes}. Default
+ * implementation is provided by the platform and allows DriverProviders to
+ * add different behaviour implementations via DriverService.
+ * </p>
+ * <p>
+ * {@link org.onosproject.net.driver.DriverData} is a container for data
+ * learned about a device. It is associated with a specific
+ * {@link org.onosproject.net.driver.Driver}
+ * and provides set of {@link org.onosproject.net.driver.Behaviour behaviours}
+ * for talking about a device. A default
+ * implementation provided by platform and has mutable key/value store for use by
+ * implementations of {@link org.onosproject.net.driver.Behaviour behaviours}.
+ * </p>
+ * <p>
+ * {@link org.onosproject.net.driver.DriverHandler} is an entity used as a
+ * context to interact with a device. It has a peer
+ * {@link org.onosproject.net.driver.DriverData} instance, which is used to
+ * store information learned about a device. It also
+ * provides set of {@link org.onosproject.net.driver.Behaviour behaviours}
+ * for talking to a device.
+ * </p>
+ * <p>
+ * {@link org.onosproject.net.driver.DriverService} can be used to query the
+ * inventory of device drivers and their behaviours, while the
+ * {@link org.onosproject.net.driver.DriverAdminService} allows adding/removing
+ * drivers and managing behaviour implementations.
+ * {@link org.onosproject.net.driver.DriverProvider} is an entity capable
+ * of add/removing drivers and supplying and managing behaviour
+ * implementations. A default implementation is provided by the framework along
+ * with a {@link org.onosproject.net.driver.XmlDriverLoader loader utility} to
+ * create a driver provider from an XML file structured as follows:
+ * <pre>
+ * &lt;drivers&gt;
+ * &lt;driver name=“...” [manufacturer="..." hwVersion="..." swVersion="..."]&gt;
+ * &lt;behaviour api="..." impl="..."/&gt;
+ * ...
+ * [&lt;property name=“key”&gt;value&lt;/key&gt;]
+ * ...
+ * &lt;/driver&gt;
+ * ...
+ * &lt;/drivers&gt;
+ * </pre>
+ *
+ */
+package org.onosproject.net.driver; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortEvent.java
new file mode 100644
index 00000000..85e6a1b5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortEvent.java
@@ -0,0 +1,59 @@
+/*
+ * 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.edge;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Describes an event pertaining to edge-port inventory.
+ */
+public class EdgePortEvent extends AbstractEvent<EdgePortEvent.Type, ConnectPoint> {
+
+ public enum Type {
+ /**
+ * Signifies that a new edge port was detected.
+ */
+ EDGE_PORT_ADDED,
+
+ /**
+ * Signifies that a new edge port vanished.
+ */
+ EDGE_PORT_REMOVED
+ }
+
+ /**
+ * Creates a new edge port event.
+ *
+ * @param type event type
+ * @param subject connection point subject
+ */
+ public EdgePortEvent(Type type, ConnectPoint subject) {
+ super(type, subject);
+ }
+
+ /**
+ * Creates a new edge port event.
+ *
+ * @param type event type
+ * @param subject connection point subject
+ * @param time occurrence time
+ */
+ public EdgePortEvent(Type type, ConnectPoint subject, long time) {
+ super(type, subject, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortListener.java
new file mode 100644
index 00000000..dae03beb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.edge;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving edge port events.
+ */
+public interface EdgePortListener extends EventListener<EdgePortEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortService.java
new file mode 100644
index 00000000..89a2c171
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/EdgePortService.java
@@ -0,0 +1,75 @@
+/*
+ * 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.edge;
+
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.nio.ByteBuffer;
+import java.util.Optional;
+
+/**
+ * Service for interacting with an inventory of network edge ports. A port
+ * is considered an edge port if it is an active port and does not have an
+ * infrastructure link associated with it.
+ */
+public interface EdgePortService
+ extends ListenerService<EdgePortEvent, EdgePortListener> {
+
+ /**
+ * Indicates whether or not the specified connection point is an edge point.
+ *
+ * @param point connection point
+ * @return true if edge point
+ */
+ boolean isEdgePoint(ConnectPoint point);
+
+ /**
+ * Returns a collection of all edge point within the current topology.
+ *
+ * @return iterable collection of all edge points
+ */
+ Iterable<ConnectPoint> getEdgePoints();
+
+ /**
+ * Returns a collection of all edge point for the specified device.
+ *
+ * @param deviceId device identifier
+ * @return iterable collection of all edge points for the device
+ */
+ Iterable<ConnectPoint> getEdgePoints(DeviceId deviceId);
+
+ /**
+ * Emits the specified packet, with optional treatment to all edge ports.
+ *
+ * @param data packet data
+ * @param treatment optional traffic treatment to apply to the packet
+ */
+ void emitPacket(ByteBuffer data, Optional<TrafficTreatment> treatment);
+
+ /**
+ * Emits the specified packet, with optional treatment to all edge ports.
+ *
+ * @param deviceId device where to send the packet out
+ * @param data packet data
+ * @param treatment optional traffic treatment to apply to the packet
+ */
+ void emitPacket(DeviceId deviceId, ByteBuffer data,
+ Optional<TrafficTreatment> treatment);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/package-info.java
new file mode 100644
index 00000000..d637f195
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/edge/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.
+ */
+
+/**
+ * Service for interacting with network edge.
+ */
+package org.onosproject.net.edge; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperation.java
new file mode 100644
index 00000000..09e34d88
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperation.java
@@ -0,0 +1,128 @@
+/*
+ * 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.flow;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A list of BatchOperationEntry.
+ *
+ * @param <T> the enum of operators <br>
+ * This enum must be defined in each sub-classes.
+ */
+public abstract class BatchOperation<T extends BatchOperationEntry<?, ?>> {
+
+ private final List<T> ops;
+
+ /**
+ * Creates new {@link BatchOperation} object.
+ */
+ public BatchOperation() {
+ ops = new LinkedList<>();
+ }
+
+ /**
+ * Creates {@link BatchOperation} object from a list of batch operation
+ * entries.
+ *
+ * @param batchOperations the list of batch operation entries.
+ */
+ public BatchOperation(Collection<T> batchOperations) {
+ ops = new LinkedList<>(checkNotNull(batchOperations));
+ }
+
+ /**
+ * Removes all operations maintained in this object.
+ */
+ public void clear() {
+ ops.clear();
+ }
+
+ /**
+ * Returns the number of operations in this object.
+ *
+ * @return the number of operations in this object
+ */
+ public int size() {
+ return ops.size();
+ }
+
+ /**
+ * Returns the operations in this object.
+ *
+ * @return the operations in this object
+ */
+ public List<T> getOperations() {
+ return Collections.unmodifiableList(ops);
+ }
+
+ /**
+ * Adds an operation.
+ * FIXME: Brian promises that the Intent Framework
+ * will not modify the batch operation after it has submitted it.
+ * Ali would prefer immutablity, but trusts brian for better or
+ * for worse.
+ *
+ * @param entry the operation to be added
+ * @return this object if succeeded, null otherwise
+ */
+ public BatchOperation<T> addOperation(T entry) {
+ return ops.add(entry) ? this : null;
+ }
+
+ /**
+ * Add all operations from another batch to this batch.
+ *
+ * @param another another batch
+ * @return true if success
+ */
+ public boolean addAll(BatchOperation<T> another) {
+ return ops.addAll(another.getOperations());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+ BatchOperation<?> other = (BatchOperation<?>) o;
+
+ return this.ops.equals(other.ops);
+ }
+
+ @Override
+ public int hashCode() {
+ return ops.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return ops.toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationEntry.java
new file mode 100644
index 00000000..cc054286
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationEntry.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A super class for batch operation entry classes.
+ * <p>
+ * This is the interface to classes which are maintained by BatchOperation as
+ * its entries.
+ */
+public class BatchOperationEntry<T extends Enum<?>, U> {
+
+ private final T operator;
+ private final U target;
+
+ /**
+ * Constructs new instance for the entry of the BatchOperation.
+ *
+ * @param operator the operator of this operation
+ * @param target the target object of this operation
+ */
+ public BatchOperationEntry(T operator, U target) {
+ this.operator = checkNotNull(operator);
+ this.target = checkNotNull(target);
+ }
+
+ /**
+ * Gets the target object of this operation.
+ *
+ * @return the target object of this operation
+ */
+ public U target() {
+ return target;
+ }
+
+ /**
+ * Gets the operator of this operation.
+ *
+ * @return the operator of this operation
+ */
+ public T operator() {
+ return operator;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BatchOperationEntry<?, ?> other = (BatchOperationEntry<?, ?>) o;
+ return (this.operator == other.operator) &&
+ Objects.equals(this.target, other.target);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(operator, target);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("operator", operator)
+ .add("target", target)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationResult.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationResult.java
new file mode 100644
index 00000000..684a8698
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/BatchOperationResult.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.net.flow;
+
+import java.util.Set;
+
+/**
+ * Interface capturing the result of a batch operation.
+ *
+ */
+public interface BatchOperationResult<T> {
+
+ /**
+ * Returns whether the operation was successful.
+ * @return true if successful, false otherwise
+ */
+ boolean isSuccess();
+
+ /**
+ * Obtains a set of items which failed.
+ * @return a set of failures
+ */
+ Set<T> failedItems();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/CompletedBatchOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/CompletedBatchOperation.java
new file mode 100644
index 00000000..3afae5ef
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/CompletedBatchOperation.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Representation of a completed flow rule batch operation.
+ */
+public class CompletedBatchOperation implements BatchOperationResult<FlowRule> {
+
+ private final boolean success;
+ private final Set<FlowRule> failures;
+ private final Set<Long> failedIds;
+ private final DeviceId deviceId;
+
+ /**
+ * Creates a new batch completion result.
+ *
+ * @param success indicates whether the completion is successful
+ * @param failures set of any failures encountered
+ * @param failedIds (optional) set of failed operation ids
+ * @param deviceId the device this operation completed for
+ */
+ public CompletedBatchOperation(boolean success, Set<? extends FlowRule> failures,
+ Set<Long> failedIds, DeviceId deviceId) {
+ this.success = success;
+ this.failures = ImmutableSet.copyOf(failures);
+ this.failedIds = ImmutableSet.copyOf(failedIds);
+ this.deviceId = deviceId;
+ }
+
+ /**
+ * Creates a new batch completion result.
+ *
+ * @param success indicates whether the completion is successful.
+ * @param failures set of any failures encountered
+ * @param deviceId the device this operation completed for
+ */
+ public CompletedBatchOperation(boolean success, Set<? extends FlowRule> failures,
+ DeviceId deviceId) {
+ this.success = success;
+ this.failures = ImmutableSet.copyOf(failures);
+ this.failedIds = Collections.emptySet();
+ this.deviceId = deviceId;
+ }
+
+
+
+ @Override
+ public boolean isSuccess() {
+ return success;
+ }
+
+ @Override
+ public Set<FlowRule> failedItems() {
+ return failures;
+ }
+
+ public Set<Long> failedIds() {
+ return failedIds;
+ }
+
+ public DeviceId deviceId() {
+ return this.deviceId;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("success?", success)
+ .add("failedItems", failures)
+ .add("failedIds", failedIds)
+ .add("deviceId", deviceId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowEntry.java
new file mode 100644
index 00000000..f7e7708e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowEntry.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.slf4j.Logger;
+
+public class DefaultFlowEntry extends DefaultFlowRule
+ implements StoredFlowEntry {
+
+ private static final Logger log = getLogger(DefaultFlowEntry.class);
+
+ private long life;
+ private long packets;
+ private long bytes;
+ private FlowEntryState state;
+
+ private long lastSeen = -1;
+
+ private final int errType;
+
+ private final int errCode;
+
+ public DefaultFlowEntry(FlowRule rule, FlowEntryState state,
+ long life, long packets, long bytes) {
+ super(rule);
+ this.state = state;
+ this.life = life;
+ this.packets = packets;
+ this.bytes = bytes;
+ this.errCode = -1;
+ this.errType = -1;
+ this.lastSeen = System.currentTimeMillis();
+ }
+
+ public DefaultFlowEntry(FlowRule rule) {
+ super(rule);
+ this.state = FlowEntryState.PENDING_ADD;
+ this.life = 0;
+ this.packets = 0;
+ this.bytes = 0;
+ this.errCode = -1;
+ this.errType = -1;
+ this.lastSeen = System.currentTimeMillis();
+ }
+
+ public DefaultFlowEntry(FlowRule rule, int errType, int errCode) {
+ super(rule);
+ this.state = FlowEntryState.FAILED;
+ this.errType = errType;
+ this.errCode = errCode;
+ this.lastSeen = System.currentTimeMillis();
+ }
+
+ @Override
+ public long life() {
+ return life;
+ }
+
+ @Override
+ public long packets() {
+ return packets;
+ }
+
+ @Override
+ public long bytes() {
+ return bytes;
+ }
+
+ @Override
+ public FlowEntryState state() {
+ return this.state;
+ }
+
+ @Override
+ public long lastSeen() {
+ return lastSeen;
+ }
+
+ @Override
+ public void setLastSeen() {
+ this.lastSeen = System.currentTimeMillis();
+ }
+
+ @Override
+ public void setState(FlowEntryState newState) {
+ this.state = newState;
+ }
+
+ @Override
+ public void setLife(long life) {
+ this.life = life;
+ }
+
+ @Override
+ public void setPackets(long packets) {
+ this.packets = packets;
+ }
+
+ @Override
+ public void setBytes(long bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public int errType() {
+ return this.errType;
+ }
+
+ @Override
+ public int errCode() {
+ return this.errCode;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("rule", super.toString())
+ .add("state", state)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowRule.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowRule.java
new file mode 100644
index 00000000..44a4d364
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultFlowRule.java
@@ -0,0 +1,390 @@
+/*
+ * 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.flow;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class DefaultFlowRule implements FlowRule {
+
+ private final DeviceId deviceId;
+ private final int priority;
+ private final TrafficSelector selector;
+ private final TrafficTreatment treatment;
+ private final long created;
+
+ private final FlowId id;
+
+ private final Short appId;
+
+ private final int timeout;
+ private final boolean permanent;
+ private final GroupId groupId;
+
+ private final Integer tableId;
+ private final FlowRuleExtPayLoad payLoad;
+
+ public DefaultFlowRule(FlowRule rule) {
+ this.deviceId = rule.deviceId();
+ this.priority = rule.priority();
+ this.selector = rule.selector();
+ this.treatment = rule.treatment();
+ this.appId = rule.appId();
+ this.groupId = rule.groupId();
+ this.id = rule.id();
+ this.timeout = rule.timeout();
+ this.permanent = rule.isPermanent();
+ this.created = System.currentTimeMillis();
+ this.tableId = rule.tableId();
+ this.payLoad = rule.payLoad();
+ }
+
+ private DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
+ TrafficTreatment treatment, Integer priority,
+ FlowId flowId, Boolean permanent, Integer timeout,
+ Integer tableId) {
+
+ this.deviceId = deviceId;
+ this.selector = selector;
+ this.treatment = treatment;
+ this.priority = priority;
+ this.appId = (short) (flowId.value() >>> 48);
+ this.id = flowId;
+ this.permanent = permanent;
+ this.timeout = timeout;
+ this.tableId = tableId;
+ this.created = System.currentTimeMillis();
+
+
+ //FIXME: fields below will be removed.
+ this.groupId = new DefaultGroupId(0);
+ this.payLoad = null;
+ }
+
+ /**
+ * Support for the third party flow rule. Creates a flow rule of flow table.
+ *
+ * @param deviceId the identity of the device where this rule applies
+ * @param selector the traffic selector that identifies what traffic this
+ * rule
+ * @param treatment the traffic treatment that applies to selected traffic
+ * @param priority the flow rule priority given in natural order
+ * @param appId the application id of this flow
+ * @param timeout the timeout for this flow requested by an application
+ * @param permanent whether the flow is permanent i.e. does not time out
+ * @param payLoad 3rd-party origin private flow
+ */
+ public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
+ TrafficTreatment treatment, int priority,
+ ApplicationId appId, int timeout, boolean permanent,
+ FlowRuleExtPayLoad payLoad) {
+
+ if (priority < FlowRule.MIN_PRIORITY) {
+ throw new IllegalArgumentException("Priority cannot be less than "
+ + MIN_PRIORITY);
+ }
+
+ this.deviceId = deviceId;
+ this.priority = priority;
+ this.selector = selector;
+ this.treatment = treatment;
+ this.appId = appId.id();
+ this.groupId = new DefaultGroupId(0);
+ this.timeout = timeout;
+ this.permanent = permanent;
+ this.tableId = 0;
+ this.created = System.currentTimeMillis();
+ this.payLoad = payLoad;
+
+ /*
+ * id consists of the following. | appId (16 bits) | groupId (16 bits) |
+ * flowId (32 bits) |
+ */
+ this.id = FlowId.valueOf((((long) this.appId) << 48)
+ | (((long) this.groupId.id()) << 32)
+ | (this.hash() & 0xffffffffL));
+ }
+
+ /**
+ * Support for the third party flow rule. Creates a flow rule of group
+ * table.
+ *
+ * @param deviceId the identity of the device where this rule applies
+ * @param selector the traffic selector that identifies what traffic this
+ * rule
+ * @param treatment the traffic treatment that applies to selected traffic
+ * @param priority the flow rule priority given in natural order
+ * @param appId the application id of this flow
+ * @param groupId the group id of this flow
+ * @param timeout the timeout for this flow requested by an application
+ * @param permanent whether the flow is permanent i.e. does not time out
+ * @param payLoad 3rd-party origin private flow
+ *
+ */
+ public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
+ TrafficTreatment treatment, int priority,
+ ApplicationId appId, GroupId groupId, int timeout,
+ boolean permanent, FlowRuleExtPayLoad payLoad) {
+
+ if (priority < FlowRule.MIN_PRIORITY) {
+ throw new IllegalArgumentException("Priority cannot be less than "
+ + MIN_PRIORITY);
+ }
+
+ this.deviceId = deviceId;
+ this.priority = priority;
+ this.selector = selector;
+ this.treatment = treatment;
+ this.appId = appId.id();
+ this.groupId = groupId;
+ this.timeout = timeout;
+ this.permanent = permanent;
+ this.created = System.currentTimeMillis();
+ this.tableId = 0;
+ this.payLoad = payLoad;
+
+ /*
+ * id consists of the following. | appId (16 bits) | groupId (16 bits) |
+ * flowId (32 bits) |
+ */
+ this.id = FlowId.valueOf((((long) this.appId) << 48)
+ | (((long) this.groupId.id()) << 32)
+ | (this.hash() & 0xffffffffL));
+ }
+
+ @Override
+ public FlowId id() {
+ return id;
+ }
+
+ @Override
+ public short appId() {
+ return appId;
+ }
+
+ @Override
+ public GroupId groupId() {
+ return groupId;
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public TrafficSelector selector() {
+ return selector;
+ }
+
+ @Override
+ public TrafficTreatment treatment() {
+ return treatment;
+ }
+
+ @Override
+ /*
+ * The priority and statistics can change on a given treatment and selector
+ *
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public int hashCode() {
+ return Objects.hash(deviceId, selector, tableId, payLoad);
+ }
+
+ //FIXME do we need this method in addition to hashCode()?
+ private int hash() {
+ return Objects.hash(deviceId, selector, tableId, payLoad);
+ }
+
+ @Override
+ /*
+ * The priority and statistics can change on a given treatment and selector
+ *
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultFlowRule) {
+ DefaultFlowRule that = (DefaultFlowRule) obj;
+ return Objects.equals(deviceId, that.deviceId) &&
+ Objects.equals(priority, that.priority) &&
+ Objects.equals(selector, that.selector) &&
+ Objects.equals(tableId, that.tableId)
+ && Objects.equals(payLoad, that.payLoad);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean exactMatch(FlowRule rule) {
+ return this.equals(rule) &&
+ Objects.equals(this.id, rule.id()) &&
+ Objects.equals(this.treatment, rule.treatment());
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("id", Long.toHexString(id.value()))
+ .add("deviceId", deviceId)
+ .add("priority", priority)
+ .add("selector", selector.criteria())
+ .add("treatment", treatment == null ? "N/A" : treatment.allInstructions())
+ .add("tableId", tableId)
+ .add("created", created)
+ .add("payLoad", payLoad)
+ .toString();
+ }
+
+ @Override
+ public int timeout() {
+ return timeout;
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public int tableId() {
+ return tableId;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder implements FlowRule.Builder {
+
+ private FlowId flowId;
+ private Integer priority;
+ private DeviceId deviceId;
+ private Integer tableId = 0;
+ private TrafficSelector selector;
+ private TrafficTreatment treatment;
+ private Integer timeout;
+ private Boolean permanent;
+
+ @Override
+ public FlowRule.Builder withCookie(long cookie) {
+ this.flowId = FlowId.valueOf(cookie);
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder fromApp(ApplicationId appId) {
+ this.flowId = computeFlowId(appId);
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder withPriority(int priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder forDevice(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder forTable(int tableId) {
+ this.tableId = tableId;
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder withSelector(TrafficSelector selector) {
+ this.selector = selector;
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder withTreatment(TrafficTreatment treatment) {
+ this.treatment = treatment;
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder makePermanent() {
+ this.timeout = 0;
+ this.permanent = true;
+ return this;
+ }
+
+ @Override
+ public FlowRule.Builder makeTemporary(int timeout) {
+ this.permanent = false;
+ this.timeout = timeout;
+ return this;
+ }
+
+ @Override
+ public FlowRule build() {
+ checkNotNull(flowId != null, "Either an application" +
+ " id or a cookie must be supplied");
+ checkNotNull(selector != null, "Traffic selector cannot be null");
+ checkNotNull(timeout != null || permanent != null, "Must either have " +
+ "a timeout or be permanent");
+ checkNotNull(deviceId != null, "Must refer to a device");
+ checkNotNull(priority != null, "Priority cannot be null");
+ checkArgument(priority >= MIN_PRIORITY, "Priority cannot be less than " +
+ MIN_PRIORITY);
+
+ return new DefaultFlowRule(deviceId, selector, treatment, priority,
+ flowId, permanent, timeout, tableId);
+ }
+
+ private FlowId computeFlowId(ApplicationId appId) {
+ return FlowId.valueOf((((long) appId.id()) << 48)
+ | (hash() & 0xffffffffL));
+ }
+
+ private int hash() {
+ return Objects.hash(deviceId, priority, selector, tableId);
+ }
+
+ }
+
+ @Override
+ public FlowRuleExtPayLoad payLoad() {
+ return payLoad;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficSelector.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficSelector.java
new file mode 100644
index 00000000..f88c6bc3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficSelector.java
@@ -0,0 +1,365 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Default traffic selector implementation.
+ */
+public final class DefaultTrafficSelector implements TrafficSelector {
+
+ private final Set<Criterion> criteria;
+
+ private static final TrafficSelector EMPTY
+ = new DefaultTrafficSelector(Collections.emptySet());
+
+ /**
+ * Creates a new traffic selector with the specified criteria.
+ *
+ * @param criteria criteria
+ */
+ private DefaultTrafficSelector(Set<Criterion> criteria) {
+ this.criteria = ImmutableSet.copyOf(criteria);
+ }
+
+ @Override
+ public Set<Criterion> criteria() {
+ return criteria;
+ }
+
+ @Override
+ public Criterion getCriterion(Criterion.Type type) {
+ for (Criterion c : criteria) {
+ if (c.type() == type) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int hashCode() {
+ return criteria.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultTrafficSelector) {
+ DefaultTrafficSelector that = (DefaultTrafficSelector) obj;
+ return Objects.equals(criteria, that.criteria);
+
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("criteria", criteria)
+ .toString();
+ }
+
+ /**
+ * Returns a new traffic selector builder.
+ *
+ * @return traffic selector builder
+ */
+ public static TrafficSelector.Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns an empty traffic selector.
+ *
+ * @return empty traffic selector
+ */
+ public static TrafficSelector emptySelector() {
+ return EMPTY;
+ }
+
+ /**
+ * Returns a new traffic selector builder primed to produce entities
+ * patterned after the supplied selector.
+ *
+ * @param selector base selector
+ * @return traffic selector builder
+ */
+ public static TrafficSelector.Builder builder(TrafficSelector selector) {
+ return new Builder(selector);
+ }
+
+ /**
+ * Builder of traffic selector entities.
+ */
+ public static final class Builder implements TrafficSelector.Builder {
+
+ private final Map<Criterion.Type, Criterion> selector = new HashMap<>();
+
+ private Builder() {
+ }
+
+ private Builder(TrafficSelector selector) {
+ for (Criterion c : selector.criteria()) {
+ add(c);
+ }
+ }
+
+ @Override
+ public Builder add(Criterion criterion) {
+ selector.put(criterion.type(), criterion);
+ return this;
+ }
+
+ @Override
+ public Builder matchInPort(PortNumber port) {
+ return add(Criteria.matchInPort(port));
+ }
+
+ @Override
+ public Builder matchInPhyPort(PortNumber port) {
+ return add(Criteria.matchInPhyPort(port));
+ }
+
+ @Override
+ public Builder matchMetadata(long metadata) {
+ return add(Criteria.matchMetadata(metadata));
+ }
+
+ @Override
+ public Builder matchEthDst(MacAddress addr) {
+ return add(Criteria.matchEthDst(addr));
+ }
+
+ @Override
+ public Builder matchEthSrc(MacAddress addr) {
+ return add(Criteria.matchEthSrc(addr));
+ }
+
+ @Override
+ public Builder matchEthType(short ethType) {
+ return add(Criteria.matchEthType(ethType));
+ }
+
+ @Override
+ public Builder matchVlanId(VlanId vlanId) {
+ return add(Criteria.matchVlanId(vlanId));
+ }
+
+ @Override
+ public Builder matchVlanPcp(byte vlanPcp) {
+ return add(Criteria.matchVlanPcp(vlanPcp));
+ }
+
+ @Override
+ public Builder matchIPDscp(byte ipDscp) {
+ return add(Criteria.matchIPDscp(ipDscp));
+ }
+
+ @Override
+ public Builder matchIPEcn(byte ipEcn) {
+ return add(Criteria.matchIPEcn(ipEcn));
+ }
+
+ @Override
+ public Builder matchIPProtocol(byte proto) {
+ return add(Criteria.matchIPProtocol(proto));
+ }
+
+ @Override
+ public Builder matchIPSrc(IpPrefix ip) {
+ return add(Criteria.matchIPSrc(ip));
+ }
+
+ @Override
+ public Builder matchIPDst(IpPrefix ip) {
+ return add(Criteria.matchIPDst(ip));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchTcpSrc(short tcpPort) {
+ return matchTcpSrc(TpPort.tpPort(tcpPort));
+ }
+
+ @Override
+ public Builder matchTcpSrc(TpPort tcpPort) {
+ return add(Criteria.matchTcpSrc(tcpPort));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchTcpDst(short tcpPort) {
+ return matchTcpDst(TpPort.tpPort(tcpPort));
+ }
+
+ @Override
+ public Builder matchTcpDst(TpPort tcpPort) {
+ return add(Criteria.matchTcpDst(tcpPort));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchUdpSrc(short udpPort) {
+ return matchUdpSrc(TpPort.tpPort(udpPort));
+ }
+
+ @Override
+ public Builder matchUdpSrc(TpPort udpPort) {
+ return add(Criteria.matchUdpSrc(udpPort));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchUdpDst(short udpPort) {
+ return matchUdpDst(TpPort.tpPort(udpPort));
+ }
+
+ @Override
+ public Builder matchUdpDst(TpPort udpPort) {
+ return add(Criteria.matchUdpDst(udpPort));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchSctpSrc(short sctpPort) {
+ return matchSctpSrc(TpPort.tpPort(sctpPort));
+ }
+
+ @Override
+ public Builder matchSctpSrc(TpPort sctpPort) {
+ return add(Criteria.matchSctpSrc(sctpPort));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchSctpDst(short sctpPort) {
+ return matchSctpDst(TpPort.tpPort(sctpPort));
+ }
+
+ @Override
+ public Builder matchSctpDst(TpPort sctpPort) {
+ return add(Criteria.matchSctpDst(sctpPort));
+ }
+
+ @Override
+ public Builder matchIcmpType(byte icmpType) {
+ return add(Criteria.matchIcmpType(icmpType));
+ }
+
+ @Override
+ public Builder matchIcmpCode(byte icmpCode) {
+ return add(Criteria.matchIcmpCode(icmpCode));
+ }
+
+ @Override
+ public Builder matchIPv6Src(IpPrefix ip) {
+ return add(Criteria.matchIPv6Src(ip));
+ }
+
+ @Override
+ public Builder matchIPv6Dst(IpPrefix ip) {
+ return add(Criteria.matchIPv6Dst(ip));
+ }
+
+ @Override
+ public Builder matchIPv6FlowLabel(int flowLabel) {
+ return add(Criteria.matchIPv6FlowLabel(flowLabel));
+ }
+
+ @Override
+ public Builder matchIcmpv6Type(byte icmpv6Type) {
+ return add(Criteria.matchIcmpv6Type(icmpv6Type));
+ }
+
+ @Override
+ public Builder matchIcmpv6Code(byte icmpv6Code) {
+ return add(Criteria.matchIcmpv6Code(icmpv6Code));
+ }
+
+ @Override
+ public Builder matchIPv6NDTargetAddress(Ip6Address targetAddress) {
+ return add(Criteria.matchIPv6NDTargetAddress(targetAddress));
+ }
+
+ @Override
+ public Builder matchIPv6NDSourceLinkLayerAddress(MacAddress mac) {
+ return add(Criteria.matchIPv6NDSourceLinkLayerAddress(mac));
+ }
+
+ @Override
+ public Builder matchIPv6NDTargetLinkLayerAddress(MacAddress mac) {
+ return add(Criteria.matchIPv6NDTargetLinkLayerAddress(mac));
+ }
+
+ @Override
+ public Builder matchMplsLabel(MplsLabel mplsLabel) {
+ return add(Criteria.matchMplsLabel(mplsLabel));
+ }
+
+ @Override
+ public Builder matchMplsBos(boolean mplsBos) {
+ return add(Criteria.matchMplsLabel(mplsBos));
+ }
+
+ @Override
+ public TrafficSelector.Builder matchTunnelId(long tunnelId) {
+ return add(Criteria.matchTunnelId(tunnelId));
+ }
+
+ @Override
+ public Builder matchIPv6ExthdrFlags(short exthdrFlags) {
+ return add(Criteria.matchIPv6ExthdrFlags(exthdrFlags));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchLambda(short lambda) {
+ return add(Criteria.matchLambda(new IndexedLambda(lambda)));
+ }
+
+ @Deprecated
+ @Override
+ public Builder matchOpticalSignalType(short signalType) {
+ return add(Criteria.matchOpticalSignalType(signalType));
+ }
+
+ @Override
+ public TrafficSelector build() {
+ return new DefaultTrafficSelector(ImmutableSet.copyOf(selector.values()));
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java
new file mode 100644
index 00000000..5d18a9ad
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java
@@ -0,0 +1,479 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.meter.MeterId;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default traffic treatment implementation.
+ */
+public final class DefaultTrafficTreatment implements TrafficTreatment {
+
+ private final List<Instruction> immediate;
+ private final List<Instruction> deferred;
+ private final List<Instruction> all;
+ private final Instructions.TableTypeTransition table;
+ private final Instructions.MetadataInstruction meta;
+
+ private final boolean hasClear;
+
+ private static final DefaultTrafficTreatment EMPTY
+ = new DefaultTrafficTreatment(Collections.emptyList());
+ private final Instructions.MeterInstruction meter;
+
+ /**
+ * Creates a new traffic treatment from the specified list of instructions.
+ *
+ * @param immediate immediate instructions
+ */
+ private DefaultTrafficTreatment(List<Instruction> immediate) {
+ this.immediate = ImmutableList.copyOf(checkNotNull(immediate));
+ this.deferred = ImmutableList.of();
+ this.all = this.immediate;
+ this.hasClear = false;
+ this.table = null;
+ this.meta = null;
+ this.meter = null;
+ }
+
+ /**
+ * Creates a new traffic treatment from the specified list of instructions.
+ *
+ * @param deferred deferred instructions
+ * @param immediate immediate instructions
+ * @param table table transition instruction
+ * @param clear instruction to clear the deferred actions list
+ */
+ private DefaultTrafficTreatment(List<Instruction> deferred,
+ List<Instruction> immediate,
+ Instructions.TableTypeTransition table,
+ boolean clear,
+ Instructions.MetadataInstruction meta,
+ Instructions.MeterInstruction meter) {
+ this.immediate = ImmutableList.copyOf(checkNotNull(immediate));
+ this.deferred = ImmutableList.copyOf(checkNotNull(deferred));
+ this.all = new ImmutableList.Builder<Instruction>()
+ .addAll(immediate)
+ .addAll(deferred)
+ .build();
+ this.table = table;
+ this.meta = meta;
+ this.hasClear = clear;
+ this.meter = meter;
+ }
+
+ @Override
+ public List<Instruction> deferred() {
+ return deferred;
+ }
+
+ @Override
+ public List<Instruction> immediate() {
+ return immediate;
+ }
+
+ @Override
+ public List<Instruction> allInstructions() {
+ return all;
+ }
+
+ @Override
+ public Instructions.TableTypeTransition tableTransition() {
+ return table;
+ }
+
+ @Override
+ public boolean clearedDeferred() {
+ return hasClear;
+ }
+
+ @Override
+ public Instructions.MetadataInstruction writeMetadata() {
+ return meta;
+ }
+
+ @Override
+ public Instructions.MeterInstruction metered() {
+ return meter;
+ }
+
+ /**
+ * Returns a new traffic treatment builder.
+ *
+ * @return traffic treatment builder
+ */
+ public static TrafficTreatment.Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns an empty traffic treatment.
+ *
+ * @return empty traffic treatment
+ */
+ public static TrafficTreatment emptyTreatment() {
+ return EMPTY;
+ }
+
+ /**
+ * Returns a new traffic treatment builder primed to produce entities
+ * patterned after the supplied treatment.
+ *
+ * @param treatment base treatment
+ * @return traffic treatment builder
+ */
+ public static TrafficTreatment.Builder builder(TrafficTreatment treatment) {
+ return new Builder(treatment);
+ }
+
+ //FIXME: Order of instructions may affect hashcode
+ @Override
+ public int hashCode() {
+ return Objects.hash(immediate, deferred, table, meta);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultTrafficTreatment) {
+ DefaultTrafficTreatment that = (DefaultTrafficTreatment) obj;
+ return Objects.equals(immediate, that.immediate) &&
+ Objects.equals(deferred, that.deferred) &&
+ Objects.equals(table, that.table) &&
+ Objects.equals(meta, that.meta);
+
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("immediate", immediate)
+ .add("deferred", deferred)
+ .add("transition", table == null ? "None" : table.toString())
+ .add("cleared", hasClear)
+ .add("metadata", meta)
+ .toString();
+ }
+
+ /**
+ * Builds a list of treatments following the following order.
+ * Modifications -&gt; Group -&gt; Output (including drop)
+ */
+ public static final class Builder implements TrafficTreatment.Builder {
+
+ boolean clear = false;
+
+ Instructions.TableTypeTransition table;
+
+ Instructions.MetadataInstruction meta;
+
+ Instructions.MeterInstruction meter;
+
+ List<Instruction> deferred = Lists.newLinkedList();
+
+ List<Instruction> immediate = Lists.newLinkedList();
+
+ List<Instruction> current = immediate;
+
+
+
+ // Creates a new builder
+ private Builder() {
+ }
+
+ // Creates a new builder based off an existing treatment
+ private Builder(TrafficTreatment treatment) {
+ deferred();
+ treatment.deferred().forEach(i -> add(i));
+
+ immediate();
+ treatment.immediate().forEach(i -> add(i));
+
+ clear = treatment.clearedDeferred();
+ }
+
+ @Override
+ public Builder add(Instruction instruction) {
+
+ switch (instruction.type()) {
+ case DROP:
+ case OUTPUT:
+ case GROUP:
+ case L0MODIFICATION:
+ case L2MODIFICATION:
+ case L3MODIFICATION:
+ case L4MODIFICATION:
+ current.add(instruction);
+ break;
+ case TABLE:
+ table = (Instructions.TableTypeTransition) instruction;
+ break;
+ case METADATA:
+ meta = (Instructions.MetadataInstruction) instruction;
+ break;
+ case METER:
+ meter = (Instructions.MeterInstruction) instruction;
+ default:
+ throw new IllegalArgumentException("Unknown instruction type: " +
+ instruction.type());
+ }
+
+ return this;
+ }
+
+ @Override
+ public Builder drop() {
+ return add(Instructions.createDrop());
+ }
+
+ @Override
+ public Builder punt() {
+ return add(Instructions.createOutput(PortNumber.CONTROLLER));
+ }
+
+ @Override
+ public Builder setOutput(PortNumber number) {
+ return add(Instructions.createOutput(number));
+ }
+
+ @Override
+ public Builder setEthSrc(MacAddress addr) {
+ return add(Instructions.modL2Src(addr));
+ }
+
+ @Override
+ public Builder setEthDst(MacAddress addr) {
+ return add(Instructions.modL2Dst(addr));
+ }
+
+ @Override
+ public Builder setVlanId(VlanId id) {
+ return add(Instructions.modVlanId(id));
+ }
+
+ @Override
+ public Builder setVlanPcp(Byte pcp) {
+ return add(Instructions.modVlanPcp(pcp));
+ }
+
+ @Override
+ public Builder setIpSrc(IpAddress addr) {
+ return add(Instructions.modL3Src(addr));
+ }
+
+ @Override
+ public Builder setIpDst(IpAddress addr) {
+ return add(Instructions.modL3Dst(addr));
+ }
+
+ @Override
+ public Builder decNwTtl() {
+ return add(Instructions.decNwTtl());
+ }
+
+ @Override
+ public Builder copyTtlIn() {
+ return add(Instructions.copyTtlIn());
+ }
+
+ @Override
+ public Builder copyTtlOut() {
+ return add(Instructions.copyTtlOut());
+ }
+
+ @Override
+ public Builder pushMpls() {
+ return add(Instructions.pushMpls());
+ }
+
+ @Override
+ public Builder popMpls() {
+ return add(Instructions.popMpls());
+ }
+
+ @Override
+ public Builder popMpls(int etherType) {
+ return add(Instructions.popMpls(new EthType(etherType)));
+ }
+
+ @Override
+ public Builder popMpls(EthType etherType) {
+ return add(Instructions.popMpls(etherType));
+ }
+
+ @Override
+ public Builder setMpls(MplsLabel mplsLabel) {
+ return add(Instructions.modMplsLabel(mplsLabel));
+ }
+
+ @Override
+ public Builder setMplsBos(boolean mplsBos) {
+ return add(Instructions.modMplsBos(mplsBos));
+ }
+
+ @Override
+ public Builder decMplsTtl() {
+ return add(Instructions.decMplsTtl());
+ }
+
+ @Deprecated
+ @Override
+ public Builder setLambda(short lambda) {
+ return add(Instructions.modL0Lambda(new IndexedLambda(lambda)));
+ }
+
+ @Override
+ public Builder group(GroupId groupId) {
+ return add(Instructions.createGroup(groupId));
+ }
+
+ @Override
+ public TrafficTreatment.Builder meter(MeterId meterId) {
+ return add(Instructions.meterTraffic(meterId));
+ }
+
+ @Override
+ public Builder popVlan() {
+ return add(Instructions.popVlan());
+ }
+
+ @Override
+ public Builder pushVlan() {
+ return add(Instructions.pushVlan());
+ }
+
+ @Override
+ public Builder transition(FlowRule.Type type) {
+ return add(Instructions.transition(type.ordinal()));
+ }
+
+ @Override
+ public Builder transition(Integer tableId) {
+ return add(Instructions.transition(tableId));
+ }
+
+ @Override
+ public Builder immediate() {
+ current = immediate;
+ return this;
+ }
+
+ @Override
+ public Builder deferred() {
+ current = deferred;
+ return this;
+ }
+
+ @Override
+ public Builder wipeDeferred() {
+ clear = true;
+ return this;
+ }
+
+ @Override
+ public Builder writeMetadata(long metadata, long metadataMask) {
+ return add(Instructions.writeMetadata(metadata, metadataMask));
+ }
+
+ @Override
+ public Builder setTunnelId(long tunnelId) {
+ return add(Instructions.modTunnelId(tunnelId));
+ }
+
+ @Deprecated
+ @Override
+ public TrafficTreatment.Builder setTcpSrc(short port) {
+ return setTcpSrc(TpPort.tpPort(port));
+ }
+
+ @Override
+ public TrafficTreatment.Builder setTcpSrc(TpPort port) {
+ return add(Instructions.modTcpSrc(port));
+ }
+
+ @Deprecated
+ @Override
+ public TrafficTreatment.Builder setTcpDst(short port) {
+ return setTcpDst(TpPort.tpPort(port));
+ }
+
+ @Override
+ public TrafficTreatment.Builder setTcpDst(TpPort port) {
+ return add(Instructions.modTcpDst(port));
+ }
+
+ @Deprecated
+ @Override
+ public TrafficTreatment.Builder setUdpSrc(short port) {
+ return setUdpSrc(TpPort.tpPort(port));
+ }
+
+ @Override
+ public TrafficTreatment.Builder setUdpSrc(TpPort port) {
+ return add(Instructions.modUdpSrc(port));
+ }
+
+ @Deprecated
+ @Override
+ public TrafficTreatment.Builder setUdpDst(short port) {
+ return setUdpDst(TpPort.tpPort(port));
+ }
+
+ @Override
+ public TrafficTreatment.Builder setUdpDst(TpPort port) {
+ return add(Instructions.modUdpDst(port));
+ }
+
+ @Override
+ public TrafficTreatment build() {
+ //Don't add DROP instruction by default when instruction
+ //set is empty. This will be handled in DefaultSingleTablePipeline
+ //driver.
+
+ //if (deferred.size() == 0 && immediate.size() == 0
+ // && table == null && !clear) {
+ // drop();
+ //}
+ return new DefaultTrafficTreatment(deferred, immediate, table, clear, meta, meter);
+ }
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowEntry.java
new file mode 100644
index 00000000..389b2142
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowEntry.java
@@ -0,0 +1,102 @@
+/*
+ * 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.flow;
+
+
+/**
+ * Represents a generalized match &amp; action pair to be applied to
+ * an infrastucture device.
+ */
+public interface FlowEntry extends FlowRule {
+
+
+ enum FlowEntryState {
+
+ /**
+ * Indicates that this rule has been submitted for addition.
+ * Not necessarily in the flow table.
+ */
+ PENDING_ADD,
+
+ /**
+ * Rule has been added which means it is in the flow table.
+ */
+ ADDED,
+
+ /**
+ * Flow has been marked for removal, might still be in flow table.
+ */
+ PENDING_REMOVE,
+
+ /**
+ * Flow has been removed from flow table and can be purged.
+ */
+ REMOVED,
+
+ /**
+ * Indicates that the installation of this flow has failed.
+ */
+ FAILED
+ }
+
+ /**
+ * Returns the flow entry state.
+ *
+ * @return flow entry state
+ */
+ FlowEntryState state();
+
+ /**
+ * Returns the number of milliseconds this flow rule has been applied.
+ *
+ * @return number of millis
+ */
+ long life();
+
+ /**
+ * Returns the number of packets this flow rule has matched.
+ *
+ * @return number of packets
+ */
+ long packets();
+
+ /**
+ * Returns the number of bytes this flow rule has matched.
+ *
+ * @return number of bytes
+ */
+ long bytes();
+
+ // TODO: consider removing this attribute
+ /**
+ * When this flow entry was last deemed active.
+ * @return epoch time of last activity
+ */
+ long lastSeen();
+
+ /**
+ * Indicates the error type.
+ * @return an integer value of the error
+ */
+ int errType();
+
+ /**
+ * Indicates the error code.
+ * @return an integer value of the error
+ */
+ int errCode();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowId.java
new file mode 100644
index 00000000..52500f59
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowId.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.net.flow;
+
+import com.google.common.base.Objects;
+
+/**
+ * Representation of a Flow ID.
+ */
+public final class FlowId {
+
+ private final long flowid;
+
+ private FlowId(long id) {
+ this.flowid = id;
+ }
+
+ public static FlowId valueOf(long id) {
+ return new FlowId(id);
+ }
+
+ public long value() {
+ return flowid;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj.getClass() == this.getClass()) {
+ FlowId that = (FlowId) obj;
+ return Objects.equal(this.flowid, that.flowid);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.flowid);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java
new file mode 100644
index 00000000..e446a9fe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java
@@ -0,0 +1,265 @@
+/*
+ * 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.flow;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Represents a generalized match &amp; action pair to be applied to an
+ * infrastructure device.
+ */
+public interface FlowRule {
+
+ static final int MAX_TIMEOUT = 60;
+ static final int MIN_PRIORITY = 0;
+
+ /**
+ * The FlowRule type is used to determine in which table the flow rule needs
+ * to be put for multi-table support switch. For single table switch,
+ * Default is used.
+ *
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ static enum Type {
+ /*
+ * Default type - used in flow rule for single table switch NOTE: this
+ * setting should not be used as Table 0 in a multi-table pipeline
+ */
+ DEFAULT,
+ /* Used in flow entry for IP table */
+ IP,
+ /* Used in flow entry for MPLS table */
+ MPLS,
+ /* Used in flow entry for ACL table */
+ ACL,
+
+ /* VLAN-to-MPLS table */
+ VLAN_MPLS,
+
+ /* VLAN table */
+ VLAN,
+
+ /* Ethtype table */
+ ETHER,
+
+ /* Class of Service table */
+ COS,
+
+ /* Table 0 in a multi-table pipeline */
+ FIRST,
+ }
+
+ /**
+ * Returns the ID of this flow.
+ *
+ * @return the flow ID
+ */
+ FlowId id();
+
+ /**
+ * Returns the application id of this flow.
+ *
+ * @return an applicationId
+ */
+ short appId();
+
+ /**
+ * Returns the group id of this flow.
+ *
+ * @return an groupId
+ */
+ GroupId groupId();
+
+ /**
+ * Returns the flow rule priority given in natural order; higher numbers
+ * mean higher priorities.
+ *
+ * @return flow rule priority
+ */
+ int priority();
+
+ /**
+ * Returns the identity of the device where this rule applies.
+ *
+ * @return device identifier
+ */
+ DeviceId deviceId();
+
+ /**
+ * Returns the traffic selector that identifies what traffic this rule
+ * should apply to.
+ *
+ * @return traffic selector
+ */
+ TrafficSelector selector();
+
+ /**
+ * Returns the traffic treatment that applies to selected traffic.
+ *
+ * @return traffic treatment
+ */
+ TrafficTreatment treatment();
+
+ /**
+ * Returns the timeout for this flow requested by an application.
+ *
+ * @return integer value of the timeout
+ */
+ int timeout();
+
+ /**
+ * Returns whether the flow is permanent i.e. does not time out.
+ *
+ * @return true if the flow is permanent, otherwise false
+ */
+ boolean isPermanent();
+
+ /**
+ * Returns the table id for this rule.
+ *
+ * @return an integer.
+ */
+ int tableId();
+
+ /**
+ * {@inheritDoc}
+ *
+ * Equality for flow rules only considers 'match equality'. This means that
+ * two flow rules with the same match conditions will be equal, regardless
+ * of the treatment or other characteristics of the flow.
+ *
+ * @param obj the reference object with which to compare.
+ * @return {@code true} if this object is the same as the obj
+ * argument; {@code false} otherwise.
+ */
+ boolean equals(Object obj);
+
+ /**
+ * Returns whether this flow rule is an exact match to the flow rule given
+ * in the argument.
+ * <p>
+ * Exact match means that deviceId, priority, selector,
+ * tableId, flowId and treatment are equal. Note that this differs from
+ * the notion of object equality for flow rules, which does not consider the
+ * flowId or treatment when testing equality.
+ * </p>
+ *
+ * @param rule other rule to match against
+ * @return true if the rules are an exact match, otherwise false
+ */
+ boolean exactMatch(FlowRule rule);
+
+ /**
+ * A flowrule builder.
+ */
+ interface Builder {
+
+ /**
+ * Assigns a cookie value to this flowrule. Mutually exclusive with the
+ * fromApp method. This method is intended to take a cookie value from
+ * the dataplane and not from the application.
+ *
+ * @param cookie a long value
+ * @return this
+ */
+ Builder withCookie(long cookie);
+
+ /**
+ * Assigns the application that built this flow rule to this object.
+ * The short value of the appId will be used as a basis for the
+ * cookie value computation. It is expected that application use this
+ * call to set their application id.
+ *
+ * @param appId an application id
+ * @return this
+ */
+ Builder fromApp(ApplicationId appId);
+
+ /**
+ * Sets the priority for this flow rule.
+ *
+ * @param priority an integer
+ * @return this
+ */
+ Builder withPriority(int priority);
+
+ /**
+ * Sets the deviceId for this flow rule.
+ *
+ * @param deviceId a device id
+ * @return this
+ */
+ Builder forDevice(DeviceId deviceId);
+
+ /**
+ * Sets the table id for this flow rule. Default value is 0.
+ *
+ * @param tableId an integer
+ * @return this
+ */
+ Builder forTable(int tableId);
+
+ /**
+ * Sets the selector (or match field) for this flow rule.
+ *
+ * @param selector a traffic selector
+ * @return this
+ */
+ Builder withSelector(TrafficSelector selector);
+
+ /**
+ * Sets the traffic treatment for this flow rule.
+ *
+ * @param treatment a traffic treatment
+ * @return this
+ */
+ Builder withTreatment(TrafficTreatment treatment);
+
+ /**
+ * Makes this rule permanent on the dataplane.
+ *
+ * @return this
+ */
+ Builder makePermanent();
+
+ /**
+ * Makes this rule temporary and timeout after the specified amount
+ * of time.
+ *
+ * @param timeout an integer
+ * @return this
+ */
+ Builder makeTemporary(int timeout);
+
+ /**
+ * Builds a flow rule object.
+ *
+ * @return a flow rule.
+ */
+ FlowRule build();
+
+ }
+
+ /**
+ * Returns the third party original flow rule.
+ *
+ * @return FlowRuleExtPayLoad
+ */
+ FlowRuleExtPayLoad payLoad();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java
new file mode 100644
index 00000000..455c6bd8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+
+@Deprecated
+/**
+ * @deprecated in Drake release - no longer a public API
+ */
+public class FlowRuleBatchEntry
+ extends BatchOperationEntry<FlowRuleOperation, FlowRule> {
+
+ private final Long id; // FIXME: consider using Optional<Long>
+
+ public FlowRuleBatchEntry(FlowRuleOperation operator, FlowRule target) {
+ super(operator, target);
+ this.id = null;
+ }
+
+ public FlowRuleBatchEntry(FlowRuleOperation operator, FlowRule target, Long id) {
+ super(operator, target);
+ this.id = id;
+ }
+
+ public Long id() {
+ return id;
+ }
+
+ public enum FlowRuleOperation {
+ ADD,
+ REMOVE,
+ MODIFY
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.java
new file mode 100644
index 00000000..2e823c23
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.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.flow;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.DeviceId;
+
+@Deprecated
+/**
+ * Describes flow rule batch event.
+ *
+ * @deprecated in Drake release - no longer a public API
+ */
+public final class FlowRuleBatchEvent extends AbstractEvent<FlowRuleBatchEvent.Type, FlowRuleBatchRequest> {
+
+
+ /**
+ * Type of flow rule events.
+ */
+ public enum Type {
+
+ // Request has been forwarded to MASTER Node
+ /**
+ * Signifies that a batch operation has been initiated.
+ */
+ BATCH_OPERATION_REQUESTED,
+
+ // MASTER Node has pushed the batch down to the Device
+ // (e.g., Received barrier reply)
+ /**
+ * Signifies that a batch operation has completed.
+ */
+ BATCH_OPERATION_COMPLETED,
+ }
+
+ private final CompletedBatchOperation result;
+ private final DeviceId deviceId;
+
+ /**
+ * Constructs a new FlowRuleBatchEvent.
+ *
+ * @param request batch operation request
+ * @param deviceId the device this batch will be processed on
+ * @return event.
+ */
+ public static FlowRuleBatchEvent requested(FlowRuleBatchRequest request, DeviceId deviceId) {
+ FlowRuleBatchEvent event = new FlowRuleBatchEvent(Type.BATCH_OPERATION_REQUESTED, request, deviceId);
+ return event;
+ }
+
+ /**
+ * Constructs a new FlowRuleBatchEvent.
+ * @param request batch operation request.
+ * @param result completed batch operation result.
+ * @return event.
+ */
+ public static FlowRuleBatchEvent completed(FlowRuleBatchRequest request, CompletedBatchOperation result) {
+ FlowRuleBatchEvent event = new FlowRuleBatchEvent(Type.BATCH_OPERATION_COMPLETED, request, result);
+ return event;
+ }
+
+ /**
+ * Returns the result of this batch operation.
+ * @return batch operation result.
+ */
+ public CompletedBatchOperation result() {
+ return result;
+ }
+
+ /**
+ * Returns the deviceId for this batch.
+ * @return device id
+ */
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ /**
+ * Creates an event of a given type and for the specified flow rule batch.
+ *
+ * @param type flow rule batch event type
+ * @param request event flow rule batch subject
+ * @param result the result of the batch operation
+ */
+ private FlowRuleBatchEvent(Type type, FlowRuleBatchRequest request, CompletedBatchOperation result) {
+ super(type, request);
+ this.result = result;
+ this.deviceId = result.deviceId();
+ }
+
+ /**
+ * Creates an event of a given type and for the specified flow rule batch.
+ *
+ * @param type flow rule batch event type
+ * @param request event flow rule batch subject
+ * @param deviceId the device id for this batch
+ */
+ private FlowRuleBatchEvent(Type type, FlowRuleBatchRequest request, DeviceId deviceId) {
+ super(type, request);
+ this.result = null;
+ this.deviceId = deviceId;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java
new file mode 100644
index 00000000..35428f46
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Collection;
+
+@Deprecated
+/**
+ * Class used with the flow subsystem to process per device
+ * batches.
+ *
+ * @deprecated in Drake release - no longer a public API
+ */
+public class FlowRuleBatchOperation
+ extends BatchOperation<FlowRuleBatchEntry> {
+
+ /**
+ * This id is used to cary to id of the original
+ * FlowOperations and track where this batch operation
+ * came from. The id is unique cluster wide.
+ */
+ private final long id;
+ private final DeviceId deviceId;
+
+ public FlowRuleBatchOperation(Collection<FlowRuleBatchEntry> operations,
+ DeviceId deviceId, long flowOperationId) {
+ super(operations);
+ this.id = flowOperationId;
+ this.deviceId = deviceId;
+ }
+
+ public DeviceId deviceId() {
+ return this.deviceId;
+ }
+
+ public long id() {
+ return id;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java
new file mode 100644
index 00000000..0b0585b9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import com.google.common.collect.Lists;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+@Deprecated
+/**
+ * @deprecated in Drake release - no longer a public API
+ */
+public class FlowRuleBatchRequest {
+
+ /**
+ * This id is used to carry to id of the original
+ * FlowOperations and track where this batch operation
+ * came from. The id is unique cluster wide.
+ */
+ private final long batchId;
+
+ private final Set<FlowRuleBatchEntry> ops;
+
+
+ public FlowRuleBatchRequest(long batchId, Set<FlowRuleBatchEntry> ops) {
+ this.batchId = batchId;
+ this.ops = Collections.unmodifiableSet(ops);
+ }
+
+ public Set<FlowRuleBatchEntry> ops() {
+ return ops;
+ }
+
+ public FlowRuleBatchOperation asBatchOperation(DeviceId deviceId) {
+ List<FlowRuleBatchEntry> entries = Lists.newArrayList();
+ entries.addAll(ops);
+ return new FlowRuleBatchOperation(entries, deviceId, batchId);
+ }
+
+ public long batchId() {
+ return batchId;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleEvent.java
new file mode 100644
index 00000000..41ef1c8a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleEvent.java
@@ -0,0 +1,78 @@
+/*
+ * 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.flow;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes flow rule event.
+ */
+public class FlowRuleEvent extends AbstractEvent<FlowRuleEvent.Type, FlowRule> {
+
+ /**
+ * Type of flow rule events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a new flow rule has been detected.
+ */
+ RULE_ADDED,
+
+ /**
+ * Signifies that a flow rule has been removed.
+ */
+ RULE_REMOVED,
+
+ /**
+ * Signifies that a rule has been updated.
+ */
+ RULE_UPDATED,
+
+ // internal event between Manager <-> Store
+
+ /*
+ * Signifies that a request to add flow rule has been added to the store.
+ */
+ RULE_ADD_REQUESTED,
+ /*
+ * Signifies that a request to remove flow rule has been added to the store.
+ */
+ RULE_REMOVE_REQUESTED,
+ }
+
+ /**
+ * Creates an event of a given type and for the specified flow rule and the
+ * current time.
+ *
+ * @param type flow rule event type
+ * @param flowRule event flow rule subject
+ */
+ public FlowRuleEvent(Type type, FlowRule flowRule) {
+ super(type, flowRule);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified flow rule and time.
+ *
+ * @param type flow rule event type
+ * @param flowRule event flow rule subject
+ * @param time occurrence time
+ */
+ public FlowRuleEvent(Type type, FlowRule flowRule, long time) {
+ super(type, flowRule, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleExtPayLoad.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleExtPayLoad.java
new file mode 100644
index 00000000..8d36be49
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleExtPayLoad.java
@@ -0,0 +1,67 @@
+package org.onosproject.net.flow;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Arrays;
+
+/**
+ * Represents for 3rd-party private original flow.
+ */
+public final class FlowRuleExtPayLoad {
+ private final byte[] payLoad;
+
+ /**
+ * private constructor.
+ *
+ * @param payLoad private flow
+ */
+ private FlowRuleExtPayLoad(byte[] payLoad) {
+ this.payLoad = payLoad;
+ }
+
+ /**
+ * Creates a FlowRuleExtPayLoad.
+ *
+ * @param payLoad payload byte data
+ * @return FlowRuleExtPayLoad payLoad
+ */
+ public static FlowRuleExtPayLoad flowRuleExtPayLoad(byte[] payLoad) {
+ return new FlowRuleExtPayLoad(payLoad);
+ }
+
+ /**
+ * Returns private flow.
+ *
+ * @return payLoad private flow
+ */
+ public byte[] payLoad() {
+ return payLoad;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(payLoad);
+ }
+
+ public int hash() {
+ return Arrays.hashCode(payLoad);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof FlowRuleExtPayLoad) {
+ FlowRuleExtPayLoad that = (FlowRuleExtPayLoad) obj;
+ return Arrays.equals(payLoad, that.payLoad);
+
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("payLoad", payLoad).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleListener.java
new file mode 100644
index 00000000..1a6ef7d9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.flow;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving flow rule related events.
+ */
+public interface FlowRuleListener extends EventListener<FlowRuleEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperation.java
new file mode 100644
index 00000000..82d43be8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperation.java
@@ -0,0 +1,67 @@
+/*
+ * 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.flow;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Representation of an operation on a flow rule table.
+ */
+public class FlowRuleOperation {
+
+ /**
+ * Type of flow table operations.
+ */
+ public enum Type {
+ ADD,
+ MODIFY,
+ REMOVE
+ }
+
+ private final FlowRule rule;
+ private final Type type;
+
+ public FlowRuleOperation(FlowRule rule, Type type) {
+ this.rule = rule;
+ this.type = type;
+ }
+
+ /**
+ * Returns the type of operation.
+ *
+ * @return type
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the flow rule.
+ *
+ * @return flow rule
+ */
+ public FlowRule rule() {
+ return rule;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("rule", rule)
+ .add("type", type)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperations.java
new file mode 100644
index 00000000..84e0b8be
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperations.java
@@ -0,0 +1,181 @@
+/*
+ * 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.flow;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.onosproject.net.flow.FlowRuleOperation.Type.*;
+
+/**
+ * A batch of flow rule operations that are broken into stages.
+ * TODO move this up to parent's package
+ */
+public class FlowRuleOperations {
+
+ private final List<Set<FlowRuleOperation>> stages;
+ private final FlowRuleOperationsContext callback; // TODO consider Optional
+
+ private FlowRuleOperations(List<Set<FlowRuleOperation>> stages,
+ FlowRuleOperationsContext cb) {
+ this.stages = stages;
+ this.callback = cb;
+ }
+
+ // kryo-constructor
+ protected FlowRuleOperations() {
+ this.stages = Lists.newArrayList();
+ this.callback = null;
+ }
+
+ /**
+ * Returns the flow rule operations as sets of stages that should be
+ * executed sequentially.
+ *
+ * @return flow rule stages
+ */
+ public List<Set<FlowRuleOperation>> stages() {
+ return stages;
+ }
+
+ /**
+ * Returns the callback for this batch of operations.
+ *
+ * @return callback
+ */
+ public FlowRuleOperationsContext callback() {
+ return callback;
+ }
+
+ /**
+ * Returns a new builder.
+ *
+ * @return new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("stages", stages)
+ .toString();
+ }
+
+ /**
+ * A builder for constructing flow rule operations.
+ */
+ public static final class Builder {
+
+ private final ImmutableList.Builder<Set<FlowRuleOperation>> listBuilder = ImmutableList.builder();
+ private ImmutableSet.Builder<FlowRuleOperation> currentStage = ImmutableSet.builder();
+
+ // prevent use of the default constructor outside of this file; use the above method
+ private Builder() {}
+
+ /**
+ * Appends a flow rule add to the current stage.
+ *
+ * @param flowRule flow rule
+ * @return this
+ */
+ public Builder add(FlowRule flowRule) {
+ currentStage.add(new FlowRuleOperation(flowRule, ADD));
+ return this;
+ }
+
+ /**
+ * Appends an existing flow rule to the current stage.
+ *
+ * @param flowRuleOperation flow rule operation
+ * @return this
+ */
+ public Builder operation(FlowRuleOperation flowRuleOperation) {
+ currentStage.add(flowRuleOperation);
+ return this;
+ }
+
+ /**
+ * Appends a flow rule modify to the current stage.
+ *
+ * @param flowRule flow rule
+ * @return this
+ */
+ public Builder modify(FlowRule flowRule) {
+ currentStage.add(new FlowRuleOperation(flowRule, MODIFY));
+ return this;
+ }
+
+ /**
+ * Appends a flow rule remove to the current stage.
+ *
+ * @param flowRule flow rule
+ * @return this
+ */
+ // FIXME this is confusing, consider renaming
+ public Builder remove(FlowRule flowRule) {
+ currentStage.add(new FlowRuleOperation(flowRule, REMOVE));
+ return this;
+ }
+
+ /**
+ * Closes the current stage.
+ */
+ private void closeStage() {
+ ImmutableSet<FlowRuleOperation> stage = currentStage.build();
+ if (!stage.isEmpty()) {
+ listBuilder.add(stage);
+ }
+ }
+
+ /**
+ * Closes the current stage and starts a new one.
+ *
+ * @return this
+ */
+ public Builder newStage() {
+ closeStage();
+ currentStage = ImmutableSet.builder();
+ return this;
+ }
+
+ /**
+ * Builds the immutable flow rule operations.
+ *
+ * @return flow rule operations
+ */
+ public FlowRuleOperations build() {
+ return build(null);
+ }
+
+ /**
+ * Builds the immutable flow rule operations.
+ *
+ * @param cb the callback to call when this operation completes
+ * @return flow rule operations
+ */
+ public FlowRuleOperations build(FlowRuleOperationsContext cb) {
+ closeStage();
+ return new FlowRuleOperations(listBuilder.build(), cb);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperationsContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperationsContext.java
new file mode 100644
index 00000000..c405b129
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleOperationsContext.java
@@ -0,0 +1,28 @@
+/*
+ * 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.flow;
+
+/**
+ * The context of a flow rule operations that will become the subject of
+ * the notification.
+ *
+ * Implementations of this class must be serializable.
+ */
+public interface FlowRuleOperationsContext {
+ // TODO we might also want to execute a method on behalf of the app
+ default void onSuccess(FlowRuleOperations ops){}
+ default void onError(FlowRuleOperations ops){}
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
new file mode 100644
index 00000000..ac2895eb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of a flow rule provider.
+ */
+public interface FlowRuleProvider extends Provider {
+
+ /**
+ * Instructs the provider to apply the specified flow rules to their
+ * respective devices.
+ * @param flowRules one or more flow rules
+ * throws SomeKindOfException that indicates which ones were applied and
+ * which ones failed
+ */
+ void applyFlowRule(FlowRule... flowRules);
+
+ /**
+ * Instructs the provider to remove the specified flow rules to their
+ * respective devices.
+ * @param flowRules one or more flow rules
+ * throws SomeKindOfException that indicates which ones were applied and
+ * which ones failed
+ */
+ void removeFlowRule(FlowRule... flowRules);
+
+ /**
+ * Removes rules by their id.
+ * @param id the id to remove
+ * @param flowRules one or more flow rules
+ */
+ void removeRulesById(ApplicationId id, FlowRule... flowRules);
+
+ /**
+ * Installs a batch of flow rules. Each flowrule is associated to an
+ * operation which results in either addition, removal or modification.
+ * @param batch a batch of flow rules
+ */
+ void executeBatch(FlowRuleBatchOperation batch);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderRegistry.java
new file mode 100644
index 00000000..7e317c01
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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.flow;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction for a flow rule provider registry.
+ */
+public interface FlowRuleProviderRegistry
+ extends ProviderRegistry<FlowRuleProvider, FlowRuleProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java
new file mode 100644
index 00000000..8a36a921
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderService;
+
+/**
+ * Service through which flow rule providers can inject information into
+ * the core.
+ */
+public interface FlowRuleProviderService extends ProviderService<FlowRuleProvider> {
+
+ /**
+ * Signals that a flow rule that was previously installed has been removed.
+ *
+ * @param flowEntry removed flow entry
+ */
+ void flowRemoved(FlowEntry flowEntry);
+
+ /**
+ * Pushes the collection of flow entries currently applied on the given
+ * device.
+ *
+ * @param deviceId device identifier
+ * @param flowEntries collection of flow rules
+ */
+ void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries);
+
+ /**
+ * Indicates to the core that the requested batch operation has
+ * been completed.
+ *
+ * @param batchId the batch which was processed
+ * @param operation the resulting outcome of the operation
+ */
+ void batchOperationCompleted(long batchId, CompletedBatchOperation operation);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleService.java
new file mode 100644
index 00000000..e2971158
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleService.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.flow;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Service for injecting flow rules into the environment and for obtaining
+ * information about flow rules already in the environment. This implements
+ * semantics of a distributed authoritative flow table where the master copy
+ * of the flow rules lies with the controller and the devices hold only the
+ * 'cached' copy.
+ */
+public interface FlowRuleService
+ extends ListenerService<FlowRuleEvent, FlowRuleListener> {
+
+ /**
+ * The topic used for obtaining globally unique ids.
+ */
+ static String FLOW_OP_TOPIC = "flow-ops-ids";
+
+ /**
+ * Returns the number of flow rules in the system.
+ *
+ * @return flow rule count
+ */
+ int getFlowRuleCount();
+
+ /**
+ * Returns the collection of flow entries applied on the specified device.
+ * This will include flow rules which may not yet have been applied to
+ * the device.
+ *
+ * @param deviceId device identifier
+ * @return collection of flow rules
+ */
+ Iterable<FlowEntry> getFlowEntries(DeviceId deviceId);
+
+ // TODO: add createFlowRule factory method and execute operations method
+
+ /**
+ * Applies the specified flow rules onto their respective devices. These
+ * flow rules will be retained by the system and re-applied anytime the
+ * device reconnects to the controller.
+ *
+ * @param flowRules one or more flow rules
+ */
+ void applyFlowRules(FlowRule... flowRules);
+
+ /**
+ * Removes the specified flow rules from their respective devices. If the
+ * device is not presently connected to the controller, these flow will
+ * be removed once the device reconnects.
+ *
+ * @param flowRules one or more flow rules
+ * throws SomeKindOfException that indicates which ones were removed and
+ * which ones failed
+ */
+ void removeFlowRules(FlowRule... flowRules);
+
+ /**
+ * Removes all rules by id.
+ *
+ * @param appId id to remove
+ */
+ void removeFlowRulesById(ApplicationId appId);
+
+ /**
+ * Returns a list of rules with this application id.
+ *
+ * @param id the id to look up
+ * @return collection of flow rules
+ */
+ Iterable<FlowRule> getFlowRulesById(ApplicationId id);
+
+ /**
+ * Returns a list of rules filterd by application and group id.
+ *
+ * @param appId the application id to lookup
+ * @param groupId the groupid to lookup
+ * @return collection of flow rules
+ */
+ Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId);
+
+ /**
+ * Applies a batch operation of FlowRules.
+ *
+ * @param ops batch operation to apply
+ */
+ void apply(FlowRuleOperations ops);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java
new file mode 100644
index 00000000..cece9893
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Store;
+
+/**
+ * Manages inventory of flow rules; not intended for direct use.
+ */
+public interface FlowRuleStore extends Store<FlowRuleBatchEvent, FlowRuleStoreDelegate> {
+
+ /**
+ * Returns the number of flow rule in the store.
+ *
+ * @return number of flow rules
+ */
+ int getFlowRuleCount();
+
+ /**
+ * Returns the stored flow.
+ *
+ * @param rule the rule to look for
+ * @return a flow rule
+ */
+ FlowEntry getFlowEntry(FlowRule rule);
+
+ /**
+ * Returns the flow entries associated with a device.
+ *
+ * @param deviceId the device ID
+ * @return the flow entries
+ */
+ Iterable<FlowEntry> getFlowEntries(DeviceId deviceId);
+
+ /**
+ * // TODO: Better description of method behavior.
+ * Stores a new flow rule without generating events.
+ *
+ * @param rule the flow rule to add
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ void storeFlowRule(FlowRule rule);
+
+ /**
+ * Stores a batch of flow rules.
+ *
+ * @param batchOperation batch of flow rules.
+ * A batch can contain flow rules for a single device only.
+ *
+ */
+ void storeBatch(FlowRuleBatchOperation batchOperation);
+
+ /**
+ * Invoked on the completion of a storeBatch operation.
+ *
+ * @param event flow rule batch event
+ */
+ void batchOperationComplete(FlowRuleBatchEvent event);
+
+ /**
+ * Marks a flow rule for deletion. Actual deletion will occur
+ * when the provider indicates that the flow has been removed.
+ *
+ * @param rule the flow rule to delete
+ */
+ void deleteFlowRule(FlowRule rule);
+
+ /**
+ * Stores a new flow rule, or updates an existing entry.
+ *
+ * @param rule the flow rule to add or update
+ * @return flow_added event, or null if just an update
+ */
+ FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule);
+
+ /**
+ * @param rule the flow entry to remove
+ * @return flow_removed event, or null if nothing removed
+ */
+ FlowRuleEvent removeFlowRule(FlowEntry rule);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java
new file mode 100644
index 00000000..c4ddb129
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.flow;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Flow rule store delegate abstraction.
+ */
+public interface FlowRuleStoreDelegate extends StoreDelegate<FlowRuleBatchEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/StoredFlowEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/StoredFlowEntry.java
new file mode 100644
index 00000000..dc0c3395
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/StoredFlowEntry.java
@@ -0,0 +1,50 @@
+/*
+ * 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.flow;
+
+
+public interface StoredFlowEntry extends FlowEntry {
+
+ /**
+ * Sets the last active epoch time.
+ */
+ void setLastSeen();
+
+ /**
+ * Sets the new state for this entry.
+ * @param newState new flow entry state.
+ */
+ void setState(FlowEntryState newState);
+
+ /**
+ * Sets how long this entry has been entered in the system.
+ * @param life epoch time
+ */
+ void setLife(long life);
+
+ /**
+ * Number of packets seen by this entry.
+ * @param packets a long value
+ */
+ void setPackets(long packets);
+
+ /**
+ * Number of bytes seen by this rule.
+ * @param bytes a long value
+ */
+ void setBytes(long bytes);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficSelector.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficSelector.java
new file mode 100644
index 00000000..f2de9a0f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficSelector.java
@@ -0,0 +1,420 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector.Builder;
+import org.onosproject.net.flow.criteria.Criterion;
+
+/**
+ * Abstraction of a slice of network traffic.
+ */
+public interface TrafficSelector {
+
+ /**
+ * Returns selection criteria as an ordered list.
+ *
+ * @return list of criteria
+ */
+ Set<Criterion> criteria();
+
+ /**
+ * Returns the selection criterion for a particular type, if it exists in
+ * this traffic selector.
+ *
+ * @param type criterion type to look up
+ * @return the criterion of the specified type if one exists, otherwise null
+ */
+ Criterion getCriterion(Criterion.Type type);
+
+ /**
+ * Builder of traffic selector entities.
+ */
+ interface Builder {
+
+ /**
+ * Adds a traffic selection criterion. If a same type criterion has
+ * already been added, it will be replaced by this one.
+ *
+ * @param criterion new criterion
+ * @return self
+ */
+ Builder add(Criterion criterion);
+
+ /**
+ * Matches an inport.
+ *
+ * @param port the inport
+ * @return a selection builder
+ */
+ Builder matchInPort(PortNumber port);
+
+ /**
+ * Matches a physical inport.
+ *
+ * @param port the physical inport
+ * @return a selection builder
+ */
+ Builder matchInPhyPort(PortNumber port);
+
+ /**
+ * Matches a metadata.
+ *
+ * @param metadata the metadata
+ * @return a selection builder
+ */
+ Builder matchMetadata(long metadata);
+
+ /**
+ * Matches a l2 dst address.
+ *
+ * @param addr a l2 address
+ * @return a selection builder
+ */
+ Builder matchEthDst(MacAddress addr);
+
+ /**
+ * Matches a l2 src address.
+ *
+ * @param addr a l2 address
+ * @return a selection builder
+ */
+ Builder matchEthSrc(MacAddress addr);
+
+ /**
+ * Matches the ethernet type.
+ *
+ * @param ethType an ethernet type
+ * @return a selection builder
+ */
+ Builder matchEthType(short ethType);
+
+ /**
+ * Matches the vlan id.
+ *
+ * @param vlanId a vlan id
+ * @return a selection builder
+ */
+ Builder matchVlanId(VlanId vlanId);
+
+ /**
+ * Matches a vlan priority.
+ *
+ * @param vlanPcp a vlan priority
+ * @return a selection builder
+ */
+ Builder matchVlanPcp(byte vlanPcp);
+
+ /**
+ * Matches an IP DSCP (6 bits in ToS field).
+ *
+ * @param ipDscp an IP DSCP value
+ * @return a selection builder
+ */
+ Builder matchIPDscp(byte ipDscp);
+
+ /**
+ * Matches an IP ECN (2 bits in ToS field).
+ *
+ * @param ipEcn an IP ECN value
+ * @return a selection builder
+ */
+ Builder matchIPEcn(byte ipEcn);
+
+ /**
+ * Matches the l3 protocol.
+ *
+ * @param proto a l3 protocol
+ * @return a selection builder
+ */
+ Builder matchIPProtocol(byte proto);
+
+ /**
+ * Matches a l3 IPv4 address.
+ *
+ * @param ip a l3 address
+ * @return a selection builder
+ */
+ Builder matchIPSrc(IpPrefix ip);
+
+ /**
+ * Matches a l3 IPv4 address.
+ *
+ * @param ip a l3 address
+ * @return a selection builder
+ */
+ Builder matchIPDst(IpPrefix ip);
+
+ /**
+ * Matches a TCP source port number.
+ *
+ * @param tcpPort a TCP source port number
+ * @return a selection builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder matchTcpSrc(short tcpPort);
+
+ /**
+ * Matches a TCP source port number.
+ *
+ * @param tcpPort a TCP source port number
+ * @return a selection builder
+ */
+ Builder matchTcpSrc(TpPort tcpPort);
+
+ /**
+ * Matches a TCP destination port number.
+ *
+ * @param tcpPort a TCP destination port number
+ * @return a selection builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder matchTcpDst(short tcpPort);
+
+ /**
+ * Matches a TCP destination port number.
+ *
+ * @param tcpPort a TCP destination port number
+ * @return a selection builder
+ */
+ Builder matchTcpDst(TpPort tcpPort);
+
+ /**
+ * Matches an UDP source port number.
+ *
+ * @param udpPort an UDP source port number
+ * @return a selection builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder matchUdpSrc(short udpPort);
+
+ /**
+ * Matches an UDP source port number.
+ *
+ * @param udpPort an UDP source port number
+ * @return a selection builder
+ */
+ Builder matchUdpSrc(TpPort udpPort);
+
+ /**
+ * Matches an UDP destination port number.
+ *
+ * @param udpPort an UDP destination port number
+ * @return a selection builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder matchUdpDst(short udpPort);
+
+ /**
+ * Matches an UDP destination port number.
+ *
+ * @param udpPort an UDP destination port number
+ * @return a selection builder
+ */
+ Builder matchUdpDst(TpPort udpPort);
+
+ /**
+ * Matches a SCTP source port number.
+ *
+ * @param sctpPort a SCTP source port number
+ * @return a selection builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder matchSctpSrc(short sctpPort);
+
+ /**
+ * Matches a SCTP source port number.
+ *
+ * @param sctpPort a SCTP source port number
+ * @return a selection builder
+ */
+ Builder matchSctpSrc(TpPort sctpPort);
+
+ /**
+ * Matches a SCTP destination port number.
+ *
+ * @param sctpPort a SCTP destination port number
+ * @return a selection builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder matchSctpDst(short sctpPort);
+
+ /**
+ * Matches a SCTP destination port number.
+ *
+ * @param sctpPort a SCTP destination port number
+ * @return a selection builder
+ */
+ Builder matchSctpDst(TpPort sctpPort);
+
+ /**
+ * Matches an ICMP type.
+ *
+ * @param icmpType an ICMP type
+ * @return a selection builder
+ */
+ Builder matchIcmpType(byte icmpType);
+
+ /**
+ * Matches an ICMP code.
+ *
+ * @param icmpCode an ICMP code
+ * @return a selection builder
+ */
+ Builder matchIcmpCode(byte icmpCode);
+
+ /**
+ * Matches a l3 IPv6 address.
+ *
+ * @param ip a l3 IPv6 address
+ * @return a selection builder
+ */
+ Builder matchIPv6Src(IpPrefix ip);
+
+ /**
+ * Matches a l3 IPv6 address.
+ *
+ * @param ip a l3 IPv6 address
+ * @return a selection builder
+ */
+ Builder matchIPv6Dst(IpPrefix ip);
+
+ /**
+ * Matches an IPv6 flow label.
+ *
+ * @param flowLabel an IPv6 flow label
+ * @return a selection builder
+ */
+ Builder matchIPv6FlowLabel(int flowLabel);
+
+ /**
+ * Matches an ICMPv6 type.
+ *
+ * @param icmpv6Type an ICMPv6 type
+ * @return a selection builder
+ */
+ Builder matchIcmpv6Type(byte icmpv6Type);
+
+ /**
+ * Matches an ICMPv6 code.
+ *
+ * @param icmpv6Code an ICMPv6 code
+ * @return a selection builder
+ */
+ Builder matchIcmpv6Code(byte icmpv6Code);
+
+ /**
+ * Matches an IPv6 Neighbor Discovery target address.
+ *
+ * @param targetAddress an IPv6 Neighbor Discovery target address
+ * @return a selection builder
+ */
+ Builder matchIPv6NDTargetAddress(Ip6Address targetAddress);
+
+ /**
+ * Matches an IPv6 Neighbor Discovery source link-layer address.
+ *
+ * @param mac an IPv6 Neighbor Discovery source link-layer address
+ * @return a selection builder
+ */
+ Builder matchIPv6NDSourceLinkLayerAddress(MacAddress mac);
+
+ /**
+ * Matches an IPv6 Neighbor Discovery target link-layer address.
+ *
+ * @param mac an IPv6 Neighbor Discovery target link-layer address
+ * @return a selection builder
+ */
+ Builder matchIPv6NDTargetLinkLayerAddress(MacAddress mac);
+
+ /**
+ * Matches on a MPLS label.
+ *
+ * @param mplsLabel a MPLS label.
+ * @return a selection builder
+ */
+ Builder matchMplsLabel(MplsLabel mplsLabel);
+
+ /**
+ * Matches on a MPLS Bottom-of-Stack indicator bit.
+ *
+ * @param mplsBos boolean value indicating BOS=1 (true) or BOS=0 (false).
+ * @return a selection builder
+ */
+ Builder matchMplsBos(boolean mplsBos);
+
+ /**
+ * Matches a tunnel id.
+ *
+ * @param tunnelId a tunnel id
+ * @return a selection builder
+ */
+ Builder matchTunnelId(long tunnelId);
+
+ /**
+ * Matches on IPv6 Extension Header pseudo-field flags.
+ *
+ * @param exthdrFlags the IPv6 Extension Header pseudo-field flags
+ * @return a selection builder
+ */
+ Builder matchIPv6ExthdrFlags(short exthdrFlags);
+
+ /**
+ * Matches an optical signal ID or lambda.
+ *
+ * @param lambda lambda
+ * @return a selection builder
+ * @deprecated in Cardinal Release.
+ * Use {@link #add(Criterion)} with an instance created
+ * by {@link org.onosproject.net.flow.criteria.Criteria#matchLambda(org.onosproject.net.Lambda)}.
+ */
+ @Deprecated
+ Builder matchLambda(short lambda);
+
+ /**
+ * Matches an optical Signal Type.
+ *
+ * @param signalType signalType
+ * @return a selection builder
+ * @deprecated in Cardinal Release.
+ * Use {@link #add(Criterion)}} with an instance created
+ * by {@link org.onosproject.net.flow.criteria.Criteria#matchOchSignalType(org.onosproject.net.OchSignalType)}.
+ */
+ @Deprecated
+ Builder matchOpticalSignalType(short signalType);
+
+ /**
+ * Builds an immutable traffic selector.
+ *
+ * @return traffic selector
+ */
+ TrafficSelector build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java
new file mode 100644
index 00000000..1ce669c2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java
@@ -0,0 +1,431 @@
+/*
+ * 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;
+
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.meter.MeterId;
+
+import java.util.List;
+
+/**
+ * Abstraction of network traffic treatment.
+ */
+public interface TrafficTreatment {
+
+ /**
+ * Returns the list of treatment instructions that will be applied
+ * further down the pipeline.
+ * @return list of treatment instructions
+ */
+ List<Instruction> deferred();
+
+ /**
+ * Returns the list of treatment instructions that will be applied
+ * immediately.
+ * @return list of treatment instructions
+ */
+ List<Instruction> immediate();
+
+ /**
+ * Returns the list of all instructions in the treatment, both immediate and
+ * deferred.
+ *
+ * @return list of treatment instructions
+ */
+ List<Instruction> allInstructions();
+
+ /**
+ * Returns the next table in the pipeline.
+ * @return a table transition; may be null.
+ */
+ Instructions.TableTypeTransition tableTransition();
+
+ /**
+ * Whether the deferred treatment instructions will be cleared
+ * by the device.
+ * @return a boolean
+ */
+ boolean clearedDeferred();
+
+ /**
+ * Returns the metadata instruction if there is one.
+ *
+ * @return a metadata instruction that may be null
+ */
+ Instructions.MetadataInstruction writeMetadata();
+
+ /**
+ * Returns the meter instruction if there is one.
+ *
+ * @return a meter instruction that may be null
+ */
+ Instructions.MeterInstruction metered();
+
+ /**
+ * Builder of traffic treatment entities.
+ */
+ interface Builder {
+
+ /**
+ * Adds an instruction to the builder.
+ *
+ * @param instruction an instruction
+ * @return a treatment builder
+ */
+ Builder add(Instruction instruction);
+
+ /**
+ * Adds a drop instruction.
+ *
+ * @return a treatment builder
+ */
+ Builder drop();
+
+ /**
+ * Adds a punt-to-controller instruction.
+ *
+ * @return a treatment builder
+ */
+ Builder punt();
+
+ /**
+ * Set the output port.
+ *
+ * @param number the out port
+ * @return a treatment builder
+ */
+ Builder setOutput(PortNumber number);
+
+ /**
+ * Sets the src l2 address.
+ *
+ * @param addr a macaddress
+ * @return a treatment builder
+ */
+ Builder setEthSrc(MacAddress addr);
+
+ /**
+ * Sets the dst l2 address.
+ *
+ * @param addr a macaddress
+ * @return a treatment builder
+ */
+ Builder setEthDst(MacAddress addr);
+
+ /**
+ * Sets the vlan id.
+ *
+ * @param id a vlanid
+ * @return a treatment builder
+ */
+ Builder setVlanId(VlanId id);
+
+ /**
+ * Sets the vlan priority.
+ *
+ * @param pcp a vlan priority
+ * @return a treatment builder
+ */
+ Builder setVlanPcp(Byte pcp);
+
+ /**
+ * Sets the src l3 address.
+ *
+ * @param addr an ip
+ * @return a treatment builder
+ */
+ Builder setIpSrc(IpAddress addr);
+
+ /**
+ * Sets the dst l3 address.
+ *
+ * @param addr an ip
+ * @return a treatment builder
+ */
+ Builder setIpDst(IpAddress addr);
+
+ /**
+ * Decrement the TTL in IP header by one.
+ *
+ * @return a treatment builder
+ */
+ Builder decNwTtl();
+
+ /**
+ * Copy the TTL to outer protocol layer.
+ *
+ * @return a treatment builder
+ */
+ Builder copyTtlOut();
+
+ /**
+ * Copy the TTL to inner protocol layer.
+ *
+ * @return a treatment builder
+ */
+ Builder copyTtlIn();
+
+ /**
+ * Push MPLS ether type.
+ *
+ * @return a treatment builder.
+ */
+ Builder pushMpls();
+
+ /**
+ * Pops MPLS ether type.
+ *
+ * @return a treatment builder.
+ */
+ Builder popMpls();
+
+ /**
+ * Pops MPLS ether type and set the new ethertype.
+ *
+ * @param etherType an ether type
+ * @return a treatment builder.
+ * @deprecated in Drake Release
+ */
+ @Deprecated
+ Builder popMpls(int etherType);
+
+ /**
+ * Pops MPLS ether type and set the new ethertype.
+ *
+ * @param etherType an ether type
+ * @return a treatment builder.
+ */
+ Builder popMpls(EthType etherType);
+
+ /**
+ * Sets the mpls label.
+ *
+ * @param mplsLabel MPLS label.
+ * @return a treatment builder.
+ */
+ Builder setMpls(MplsLabel mplsLabel);
+
+ /**
+ * Sets the mpls bottom-of-stack indicator bit.
+ *
+ * @param mplsBos boolean to set BOS=1 (true) or BOS=0 (false).
+ * @return a treatment builder.
+ */
+ Builder setMplsBos(boolean mplsBos);
+
+ /**
+ * Decrement MPLS TTL.
+ *
+ * @return a treatment builder
+ */
+ Builder decMplsTtl();
+
+ /**
+ * Sets the optical channel ID or lambda.
+ *
+ * @param lambda optical channel ID
+ * @return a treatment builder
+ * @deprecated in Drake Release
+ */
+ @Deprecated
+ Builder setLambda(short lambda);
+
+ /**
+ * Sets the group ID.
+ *
+ * @param groupId group ID
+ * @return a treatment builder
+ */
+ Builder group(GroupId groupId);
+
+ /**
+ * Sets a meter to be used by this flow.
+ *
+ * @param meterId a meter id
+ * @return a treatment builder
+ */
+ Builder meter(MeterId meterId);
+
+ /**
+ * Sets the next table type to transition to.
+ *
+ * @param type the table type
+ * @return a treatement builder
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ Builder transition(FlowRule.Type type);
+
+ /**
+ * Sets the next table id to transition to.
+ *
+ * @param tableId the table table
+ * @return a treatement builder
+ */
+ Builder transition(Integer tableId);
+
+
+ /**
+ * Pops outermost VLAN tag.
+ *
+ * @return a treatment builder.
+ */
+ Builder popVlan();
+
+ /**
+ * Pushes a new VLAN tag.
+ *
+ * @return a treatment builder.
+ */
+ Builder pushVlan();
+
+ /**
+ * Any instructions preceded by this method call will be deferred.
+ * @return a treatment builder
+ */
+ Builder deferred();
+
+ /**
+ * Any instructions preceded by this method call will be immediate.
+ * @return a treatment builder
+ */
+ Builder immediate();
+
+
+ /**
+ * Instructs the device to clear the deferred instructions set.
+ * @return a treatment builder
+ */
+ Builder wipeDeferred();
+
+ /**
+ * Writes metadata to associate with a packet.
+ * <pre>
+ * {@code
+ * new_metadata = (old_metadata & ̃mask) | (value & mask)
+ * }
+ * </pre>
+ *
+ * @param value the metadata to write
+ * @param mask the masked bits for the value
+ * @return a treatment builder
+ */
+ Builder writeMetadata(long value, long mask);
+
+ /**
+ * Sets the tunnel id.
+ *
+ * @param tunnelId a tunnel id.
+ * @return a treatment builder.
+ */
+ Builder setTunnelId(long tunnelId);
+
+ /**
+ * Sets the src TCP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder setTcpSrc(short port);
+
+ /**
+ * Sets the src TCP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ */
+ Builder setTcpSrc(TpPort port);
+
+ /**
+ * Sets the dst TCP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder setTcpDst(short port);
+
+ /**
+ * Sets the dst TCP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ */
+ Builder setTcpDst(TpPort port);
+
+ /**
+ * Sets the src UDP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder setUdpSrc(short port);
+
+ /**
+ * Sets the src UDP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ */
+ Builder setUdpSrc(TpPort port);
+
+ /**
+ * Sets the dst UDP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ Builder setUdpDst(short port);
+
+ /**
+ * Sets the dst UDP port.
+ *
+ * @param port a port number
+ * @return a treatment builder
+ */
+ Builder setUdpDst(TpPort port);
+
+ /**
+ * Builds an immutable traffic treatment descriptor.
+ * <p>
+ * If the treatment is empty when build() is called, it will add a default
+ * drop rule automatically. For a treatment that is actually empty, use
+ * {@link org.onosproject.net.flow.DefaultTrafficTreatment#emptyTreatment}.
+ * </p>
+ *
+ * @return traffic treatment
+ */
+ TrafficTreatment build();
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/Treatment.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/Treatment.java
new file mode 100644
index 00000000..a77079ce
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/Treatment.java
@@ -0,0 +1,36 @@
+/*
+ * 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.flow;
+
+import org.onosproject.net.PortNumber;
+
+/**
+ * Abstraction of different kinds of treatment that can be applied to an
+ * outbound packet.
+ */
+public interface Treatment {
+
+ // TODO: implement these later: modifications, group
+ // TODO: elsewhere provide factory methods for some default treatments
+
+ /**
+ * Returns the port number where the packet should be emitted.
+ *
+ * @return output port number
+ */
+ PortNumber output();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criteria.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criteria.java
new file mode 100644
index 00000000..0252cfbc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criteria.java
@@ -0,0 +1,527 @@
+/*
+ * 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.criteria;
+
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.criteria.Criterion.Type;
+import org.onosproject.net.OchSignalType;
+
+/**
+ * Factory class to create various traffic selection criteria.
+ */
+public final class Criteria {
+
+ //TODO: incomplete type implementation. Need to implement complete list from Criterion
+
+ // Ban construction
+ private Criteria() {
+ }
+
+ /**
+ * Creates a match on IN_PORT field using the specified value.
+ *
+ * @param port inport value
+ * @return match criterion
+ */
+ public static Criterion matchInPort(PortNumber port) {
+ return new PortCriterion(port, Type.IN_PORT);
+ }
+
+ /**
+ * Creates a match on IN_PHY_PORT field using the specified value.
+ *
+ * @param port inport value
+ * @return match criterion
+ */
+ public static Criterion matchInPhyPort(PortNumber port) {
+ return new PortCriterion(port, Type.IN_PHY_PORT);
+ }
+
+ /**
+ * Creates a match on METADATA field using the specified value.
+ *
+ * @param metadata metadata value (64 bits data)
+ * @return match criterion
+ */
+ public static Criterion matchMetadata(long metadata) {
+ return new MetadataCriterion(metadata);
+ }
+
+ /**
+ * Creates a match on ETH_DST field using the specified value. This value
+ * may be a wildcard mask.
+ *
+ * @param mac MAC address value or wildcard mask
+ * @return match criterion
+ */
+ public static Criterion matchEthDst(MacAddress mac) {
+ return new EthCriterion(mac, Type.ETH_DST);
+ }
+
+ /**
+ * Creates a match on ETH_SRC field using the specified value. This value
+ * may be a wildcard mask.
+ *
+ * @param mac MAC address value or wildcard mask
+ * @return match criterion
+ */
+ public static Criterion matchEthSrc(MacAddress mac) {
+ return new EthCriterion(mac, Type.ETH_SRC);
+ }
+
+ /**
+ * Creates a match on ETH_TYPE field using the specified value.
+ *
+ * @param ethType eth type value (16 bits unsigned integer)
+ * @return match criterion
+ */
+ public static Criterion matchEthType(int ethType) {
+ return new EthTypeCriterion(ethType);
+ }
+
+ /**
+ * Creates a match on ETH_TYPE field using the specified value.
+ *
+ * @param ethType eth type value
+ * @return match criterion
+ */
+ public static Criterion matchEthType(EthType ethType) {
+ return new EthTypeCriterion(ethType);
+ }
+
+ /**
+ * Creates a match on VLAN ID field using the specified value.
+ *
+ * @param vlanId vlan id value
+ * @return match criterion
+ */
+ public static Criterion matchVlanId(VlanId vlanId) {
+ return new VlanIdCriterion(vlanId);
+ }
+
+ /**
+ * Creates a match on VLAN PCP field using the specified value.
+ *
+ * @param vlanPcp vlan pcp value (3 bits)
+ * @return match criterion
+ */
+ public static Criterion matchVlanPcp(byte vlanPcp) {
+ return new VlanPcpCriterion(vlanPcp);
+ }
+
+ /**
+ * Creates a match on IP DSCP field using the specified value.
+ *
+ * @param ipDscp ip dscp value (6 bits)
+ * @return match criterion
+ */
+ public static Criterion matchIPDscp(byte ipDscp) {
+ return new IPDscpCriterion(ipDscp);
+ }
+
+ /**
+ * Creates a match on IP ECN field using the specified value.
+ *
+ * @param ipEcn ip ecn value (2 bits)
+ * @return match criterion
+ */
+ public static Criterion matchIPEcn(byte ipEcn) {
+ return new IPEcnCriterion(ipEcn);
+ }
+
+ /**
+ * Creates a match on IP proto field using the specified value.
+ *
+ * @param proto ip protocol value (8 bits unsigned integer)
+ * @return match criterion
+ */
+ public static Criterion matchIPProtocol(short proto) {
+ return new IPProtocolCriterion(proto);
+ }
+
+ /**
+ * Creates a match on IPv4 source field using the specified value.
+ *
+ * @param ip ipv4 source value
+ * @return match criterion
+ */
+ public static Criterion matchIPSrc(IpPrefix ip) {
+ return new IPCriterion(ip, Type.IPV4_SRC);
+ }
+
+ /**
+ * Creates a match on IPv4 destination field using the specified value.
+ *
+ * @param ip ipv4 source value
+ * @return match criterion
+ */
+ public static Criterion matchIPDst(IpPrefix ip) {
+ return new IPCriterion(ip, Type.IPV4_DST);
+ }
+
+ /**
+ * Creates a match on TCP source port field using the specified value.
+ *
+ * @param tcpPort TCP source port
+ * @return match criterion
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static Criterion matchTcpSrc(short tcpPort) {
+ return new TcpPortCriterion(TpPort.tpPort(tcpPort), Type.TCP_SRC);
+ }
+
+ /**
+ * Creates a match on TCP source port field using the specified value.
+ *
+ * @param tcpPort TCP source port
+ * @return match criterion
+ */
+ public static Criterion matchTcpSrc(TpPort tcpPort) {
+ return new TcpPortCriterion(tcpPort, Type.TCP_SRC);
+ }
+
+ /**
+ * Creates a match on TCP destination port field using the specified value.
+ *
+ * @param tcpPort TCP destination port
+ * @return match criterion
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static Criterion matchTcpDst(short tcpPort) {
+ return new TcpPortCriterion(TpPort.tpPort(tcpPort), Type.TCP_DST);
+ }
+
+ /**
+ * Creates a match on TCP destination port field using the specified value.
+ *
+ * @param tcpPort TCP destination port
+ * @return match criterion
+ */
+ public static Criterion matchTcpDst(TpPort tcpPort) {
+ return new TcpPortCriterion(tcpPort, Type.TCP_DST);
+ }
+
+ /**
+ * Creates a match on UDP source port field using the specified value.
+ *
+ * @param udpPort UDP source port
+ * @return match criterion
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static Criterion matchUdpSrc(short udpPort) {
+ return new UdpPortCriterion(TpPort.tpPort(udpPort), Type.UDP_SRC);
+ }
+
+ /**
+ * Creates a match on UDP source port field using the specified value.
+ *
+ * @param udpPort UDP source port
+ * @return match criterion
+ */
+ public static Criterion matchUdpSrc(TpPort udpPort) {
+ return new UdpPortCriterion(udpPort, Type.UDP_SRC);
+ }
+
+ /**
+ * Creates a match on UDP destination port field using the specified value.
+ *
+ * @param udpPort UDP destination port
+ * @return match criterion
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static Criterion matchUdpDst(short udpPort) {
+ return new UdpPortCriterion(TpPort.tpPort(udpPort), Type.UDP_DST);
+ }
+
+ /**
+ * Creates a match on UDP destination port field using the specified value.
+ *
+ * @param udpPort UDP destination port
+ * @return match criterion
+ */
+ public static Criterion matchUdpDst(TpPort udpPort) {
+ return new UdpPortCriterion(udpPort, Type.UDP_DST);
+ }
+
+ /**
+ * Creates a match on SCTP source port field using the specified value.
+ *
+ * @param sctpPort SCTP source port
+ * @return match criterion
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static Criterion matchSctpSrc(short sctpPort) {
+ return new SctpPortCriterion(TpPort.tpPort(sctpPort), Type.SCTP_SRC);
+ }
+
+ /**
+ * Creates a match on SCTP source port field using the specified value.
+ *
+ * @param sctpPort SCTP source port
+ * @return match criterion
+ */
+ public static Criterion matchSctpSrc(TpPort sctpPort) {
+ return new SctpPortCriterion(sctpPort, Type.SCTP_SRC);
+ }
+
+ /**
+ * Creates a match on SCTP destination port field using the specified
+ * value.
+ *
+ * @param sctpPort SCTP destination port
+ * @return match criterion
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static Criterion matchSctpDst(short sctpPort) {
+ return new SctpPortCriterion(TpPort.tpPort(sctpPort), Type.SCTP_DST);
+ }
+
+ /**
+ * Creates a match on SCTP destination port field using the specified
+ * value.
+ *
+ * @param sctpPort SCTP destination port
+ * @return match criterion
+ */
+ public static Criterion matchSctpDst(TpPort sctpPort) {
+ return new SctpPortCriterion(sctpPort, Type.SCTP_DST);
+ }
+
+ /**
+ * Creates a match on ICMP type field using the specified value.
+ *
+ * @param icmpType ICMP type (8 bits unsigned integer)
+ * @return match criterion
+ */
+ public static Criterion matchIcmpType(short icmpType) {
+ return new IcmpTypeCriterion(icmpType);
+ }
+
+ /**
+ * Creates a match on ICMP code field using the specified value.
+ *
+ * @param icmpCode ICMP code (8 bits unsigned integer)
+ * @return match criterion
+ */
+ public static Criterion matchIcmpCode(short icmpCode) {
+ return new IcmpCodeCriterion(icmpCode);
+ }
+
+ /**
+ * Creates a match on IPv6 source field using the specified value.
+ *
+ * @param ip ipv6 source value
+ * @return match criterion
+ */
+ public static Criterion matchIPv6Src(IpPrefix ip) {
+ return new IPCriterion(ip, Type.IPV6_SRC);
+ }
+
+ /**
+ * Creates a match on IPv6 destination field using the specified value.
+ *
+ * @param ip ipv6 destination value
+ * @return match criterion
+ */
+ public static Criterion matchIPv6Dst(IpPrefix ip) {
+ return new IPCriterion(ip, Type.IPV6_DST);
+ }
+
+ /**
+ * Creates a match on IPv6 flow label field using the specified value.
+ *
+ * @param flowLabel IPv6 flow label (20 bits)
+ * @return match criterion
+ */
+ public static Criterion matchIPv6FlowLabel(int flowLabel) {
+ return new IPv6FlowLabelCriterion(flowLabel);
+ }
+
+ /**
+ * Creates a match on ICMPv6 type field using the specified value.
+ *
+ * @param icmpv6Type ICMPv6 type (8 bits unsigned integer)
+ * @return match criterion
+ */
+ public static Criterion matchIcmpv6Type(short icmpv6Type) {
+ return new Icmpv6TypeCriterion(icmpv6Type);
+ }
+
+ /**
+ * Creates a match on ICMPv6 code field using the specified value.
+ *
+ * @param icmpv6Code ICMPv6 code (8 bits unsigned integer)
+ * @return match criterion
+ */
+ public static Criterion matchIcmpv6Code(short icmpv6Code) {
+ return new Icmpv6CodeCriterion(icmpv6Code);
+ }
+
+ /**
+ * Creates a match on IPv6 Neighbor Discovery target address using the
+ * specified value.
+ *
+ * @param targetAddress IPv6 Neighbor Discovery target address
+ * @return match criterion
+ */
+ public static Criterion matchIPv6NDTargetAddress(Ip6Address targetAddress) {
+ return new IPv6NDTargetAddressCriterion(targetAddress);
+ }
+
+ /**
+ * Creates a match on IPv6 Neighbor Discovery source link-layer address
+ * using the specified value.
+ *
+ * @param mac IPv6 Neighbor Discovery source link-layer address
+ * @return match criterion
+ */
+ public static Criterion matchIPv6NDSourceLinkLayerAddress(MacAddress mac) {
+ return new IPv6NDLinkLayerAddressCriterion(mac, Type.IPV6_ND_SLL);
+ }
+
+ /**
+ * Creates a match on IPv6 Neighbor Discovery target link-layer address
+ * using the specified value.
+ *
+ * @param mac IPv6 Neighbor Discovery target link-layer address
+ * @return match criterion
+ */
+ public static Criterion matchIPv6NDTargetLinkLayerAddress(MacAddress mac) {
+ return new IPv6NDLinkLayerAddressCriterion(mac, Type.IPV6_ND_TLL);
+ }
+
+ /**
+ * Creates a match on MPLS label.
+ *
+ * @param mplsLabel MPLS label (20 bits)
+ * @return match criterion
+ */
+ public static Criterion matchMplsLabel(MplsLabel mplsLabel) {
+ return new MplsCriterion(mplsLabel);
+ }
+
+ /**
+ * Creates a match on MPLS Bottom-of-Stack indicator bit.
+ *
+ * @param mplsBos boolean value indicating true (BOS=1) or false (BOS=0)
+ * @return match criterion
+ */
+ public static Criterion matchMplsLabel(boolean mplsBos) {
+ return new MplsBosCriterion(mplsBos);
+ }
+
+ /**
+ * Creates a match on Tunnel ID.
+ *
+ * @param tunnelId Tunnel ID (64 bits)
+ * @return match criterion
+ */
+ public static Criterion matchTunnelId(long tunnelId) {
+ return new TunnelIdCriterion(tunnelId);
+ }
+
+ /**
+ * Creates a match on IPv6 Extension Header pseudo-field fiags.
+ * Those are defined in Criterion.IPv6ExthdrFlags.
+ *
+ * @param exthdrFlags IPv6 Extension Header pseudo-field flags (16 bits)
+ * @return match criterion
+ */
+ public static Criterion matchIPv6ExthdrFlags(int exthdrFlags) {
+ return new IPv6ExthdrFlagsCriterion(exthdrFlags);
+ }
+
+ /**
+ * Creates a match on lambda field using the specified value.
+ *
+ * @param lambda lambda to match on (16 bits unsigned integer)
+ * @return match criterion
+ * @deprecated in Cardinal Release. Use {@link #matchLambda(Lambda)} instead.
+ */
+ @Deprecated
+ public static Criterion matchLambda(int lambda) {
+ return new LambdaCriterion(lambda, Type.OCH_SIGID);
+ }
+
+ /**
+ * Creates a match on lambda using the specified value.
+ *
+ * @param lambda lambda
+ * @return match criterion
+ */
+ public static Criterion matchLambda(Lambda lambda) {
+ if (lambda instanceof IndexedLambda) {
+ return new IndexedLambdaCriterion((IndexedLambda) lambda);
+ } else if (lambda instanceof OchSignal) {
+ return new OchSignalCriterion((OchSignal) lambda);
+ } else {
+ throw new UnsupportedOperationException(String.format("Unsupported type of Lambda: %s", lambda));
+ }
+ }
+
+ /**
+ * Creates a match on optical signal type using the specified value.
+ *
+ * @param sigType optical signal type (8 bits unsigned integer)
+ * @return match criterion
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ public static Criterion matchOpticalSignalType(short sigType) {
+ return new OpticalSignalTypeCriterion(sigType, Type.OCH_SIGTYPE);
+ }
+
+ /**
+ * Create a match on OCh (Optical Channel) signal type.
+ *
+ * @param signalType OCh signal type
+ * @return match criterion
+ */
+ public static Criterion matchOchSignalType(OchSignalType signalType) {
+ return new OchSignalTypeCriterion(signalType);
+ }
+
+ public static Criterion dummy() {
+ return new DummyCriterion();
+ }
+
+ /**
+ * Dummy Criterion used with @see{FilteringObjective}.
+ */
+ private static class DummyCriterion implements Criterion {
+
+ @Override
+ public Type type() {
+ return Type.DUMMY;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criterion.java
new file mode 100644
index 00000000..12ab57de
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Criterion.java
@@ -0,0 +1,181 @@
+/*
+ * 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.criteria;
+
+
+/**
+ * Representation of a single header field selection.
+ */
+public interface Criterion {
+
+ /**
+ * Types of fields to which the selection criterion may apply.
+ */
+ // From page 75 of OpenFlow 1.5.0 spec
+ enum Type {
+ /** Switch input port. */
+ IN_PORT,
+ /** Switch physical input port. */
+ IN_PHY_PORT,
+ /** Metadata passed between tables. */
+ METADATA,
+ /** Ethernet destination address. */
+ ETH_DST,
+ /** Ethernet source address. */
+ ETH_SRC,
+ /** Ethernet frame type. */
+ ETH_TYPE,
+ /** VLAN id. */
+ VLAN_VID,
+ /** VLAN priority. */
+ VLAN_PCP,
+ /** IP DSCP (6 bits in ToS field). */
+ IP_DSCP,
+ /** IP ECN (2 bits in ToS field). */
+ IP_ECN,
+ /** IP protocol. */
+ IP_PROTO,
+ /** IPv4 source address. */
+ IPV4_SRC,
+ /** IPv4 destination address. */
+ IPV4_DST,
+ /** TCP source port. */
+ TCP_SRC,
+ /** TCP destination port. */
+ TCP_DST,
+ /** UDP source port. */
+ UDP_SRC,
+ /** UDP destination port. */
+ UDP_DST,
+ /** SCTP source port. */
+ SCTP_SRC,
+ /** SCTP destination port. */
+ SCTP_DST,
+ /** ICMP type. */
+ ICMPV4_TYPE,
+ /** ICMP code. */
+ ICMPV4_CODE,
+ /** ARP opcode. */
+ ARP_OP,
+ /** ARP source IPv4 address. */
+ ARP_SPA,
+ /** ARP target IPv4 address. */
+ ARP_TPA,
+ /** ARP source hardware address. */
+ ARP_SHA,
+ /** ARP target hardware address. */
+ ARP_THA,
+ /** IPv6 source address. */
+ IPV6_SRC,
+ /** IPv6 destination address. */
+ IPV6_DST,
+ /** IPv6 Flow Label. */
+ IPV6_FLABEL,
+ /** ICMPv6 type. */
+ ICMPV6_TYPE,
+ /** ICMPv6 code. */
+ ICMPV6_CODE,
+ /** Target address for ND. */
+ IPV6_ND_TARGET,
+ /** Source link-layer for ND. */
+ IPV6_ND_SLL,
+ /** Target link-layer for ND. */
+ IPV6_ND_TLL,
+ /** MPLS label. */
+ MPLS_LABEL,
+ /** MPLS TC. */
+ MPLS_TC,
+ /** MPLS BoS bit. */
+ MPLS_BOS,
+ /** PBB I-SID. */
+ PBB_ISID,
+ /** Logical Port Metadata. */
+ TUNNEL_ID,
+ /** IPv6 Extension Header pseudo-field. */
+ IPV6_EXTHDR,
+ /** Unassigned value: 40. */
+ UNASSIGNED_40,
+ /** PBB UCA header field. */
+ PBB_UCA,
+ /** TCP flags. */
+ TCP_FLAGS,
+ /** Output port from action set metadata. */
+ ACTSET_OUTPUT,
+ /** Packet type value. */
+ PACKET_TYPE,
+
+ //
+ // NOTE: Everything below is defined elsewhere: ONOS-specific,
+ // extensions, etc.
+ //
+ /** Optical channel signal ID (lambda). */
+ OCH_SIGID,
+ /** Optical channel signal type (fixed or flexible). */
+ OCH_SIGTYPE,
+
+ /**
+ * An empty criterion.
+ */
+ DUMMY
+ }
+
+ /**
+ * Returns the type of criterion.
+ *
+ * @return type of criterion
+ */
+ Type type();
+
+ /**
+ * Bit definitions for IPv6 Extension Header pseudo-field.
+ * From page 79 of OpenFlow 1.5.0 spec.
+ */
+ enum IPv6ExthdrFlags {
+ /** "No next header" encountered. */
+ NONEXT((short) (1 << 0)),
+ /** Encrypted Sec Payload header present. */
+ ESP((short) (1 << 1)),
+ /** Authentication header present. */
+ AUTH((short) (1 << 2)),
+ /** 1 or 2 dest headers present. */
+ DEST((short) (1 << 3)),
+ /** Fragment header present. */
+ FRAG((short) (1 << 4)),
+ /** Router header present. */
+ ROUTER((short) (1 << 5)),
+ /** Hop-by-hop header present. */
+ HOP((short) (1 << 6)),
+ /** Unexpected repeats encountered. */
+ UNREP((short) (1 << 7)),
+ /** Unexpected sequencing encountered. */
+ UNSEQ((short) (1 << 8));
+
+ private short value;
+
+ IPv6ExthdrFlags(short value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value as an integer.
+ *
+ * @return the value as an integer
+ */
+ public short getValue() {
+ return this.value;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthCriterion.java
new file mode 100644
index 00000000..6020974d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.MacAddress;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of MAC address criterion.
+ */
+public final class EthCriterion implements Criterion {
+ private final MacAddress mac;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param mac the source or destination MAC address to match
+ * @param type the match type. Should be either Type.ETH_DST or
+ * Type.ETH_SRC
+ */
+ EthCriterion(MacAddress mac, Type type) {
+ this.mac = mac;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the MAC address to match.
+ *
+ * @return the MAC address to match
+ */
+ public MacAddress mac() {
+ return this.mac;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("mac", mac).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type.ordinal(), mac);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof EthCriterion) {
+ EthCriterion that = (EthCriterion) obj;
+ return Objects.equals(mac, that.mac) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthTypeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthTypeCriterion.java
new file mode 100644
index 00000000..b2666d4d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/EthTypeCriterion.java
@@ -0,0 +1,89 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.EthType;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of Ethernet type criterion (16 bits unsigned integer).
+ */
+public final class EthTypeCriterion implements Criterion {
+
+
+ private final EthType ethType;
+
+ /**
+ * Constructor.
+ *
+ * @param ethType the Ethernet frame type to match (16 bits unsigned
+ * integer)
+ */
+ EthTypeCriterion(int ethType) {
+ this.ethType = new EthType(ethType);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param ethType the Ethernet frame type to match
+ */
+ EthTypeCriterion(EthType ethType) {
+ this.ethType = ethType;
+ }
+
+ @Override
+ public Type type() {
+ return Type.ETH_TYPE;
+ }
+
+ /**
+ * Gets the Ethernet frame type to match.
+ *
+ * @return the Ethernet frame type to match (16 bits unsigned integer)
+ */
+ public EthType ethType() {
+ return ethType;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("ethType", ethType.toString())
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), ethType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof EthTypeCriterion) {
+ EthTypeCriterion that = (EthTypeCriterion) obj;
+ return Objects.equals(ethType, that.ethType) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPCriterion.java
new file mode 100644
index 00000000..018afe80
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IP address criterion.
+ */
+public final class IPCriterion implements Criterion {
+ private final IpPrefix ip;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param ip the IP prefix to match. Could be either IPv4 or IPv6
+ * @param type the match type. Should be one of the following:
+ * Type.IPV4_SRC, Type.IPV4_DST, Type.IPV6_SRC, Type.IPV6_DST
+ */
+ IPCriterion(IpPrefix ip, Type type) {
+ this.ip = ip;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the IP prefix to match.
+ *
+ * @return the IP prefix to match
+ */
+ public IpPrefix ip() {
+ return this.ip;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("ip", ip).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), ip);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPCriterion) {
+ IPCriterion that = (IPCriterion) obj;
+ return Objects.equals(ip, that.ip) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPDscpCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPDscpCriterion.java
new file mode 100644
index 00000000..8634aa66
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPDscpCriterion.java
@@ -0,0 +1,76 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IP DSCP (Differentiated Services Code Point)
+ * criterion (6 bits).
+ */
+public final class IPDscpCriterion implements Criterion {
+ private static final byte MASK = 0x3f;
+ private final byte ipDscp; // IP DSCP value: 6 bits
+
+ /**
+ * Constructor.
+ *
+ * @param ipDscp the IP DSCP value to match
+ */
+ IPDscpCriterion(byte ipDscp) {
+ this.ipDscp = (byte) (ipDscp & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.IP_DSCP;
+ }
+
+ /**
+ * Gets the IP DSCP value to match.
+ *
+ * @return the IP DSCP value to match
+ */
+ public byte ipDscp() {
+ return ipDscp;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("ipDscp", Long.toHexString(ipDscp)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), ipDscp);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPDscpCriterion) {
+ IPDscpCriterion that = (IPDscpCriterion) obj;
+ return Objects.equals(ipDscp, that.ipDscp) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPEcnCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPEcnCriterion.java
new file mode 100644
index 00000000..48b3fbf6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPEcnCriterion.java
@@ -0,0 +1,76 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IP ECN (Explicit Congestion Notification) criterion
+ * (2 bits).
+ */
+public final class IPEcnCriterion implements Criterion {
+ private static final byte MASK = 0x3;
+ private final byte ipEcn; // IP ECN value: 2 bits
+
+ /**
+ * Constructor.
+ *
+ * @param ipEcn the IP ECN value to match (2 bits)
+ */
+ IPEcnCriterion(byte ipEcn) {
+ this.ipEcn = (byte) (ipEcn & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.IP_ECN;
+ }
+
+ /**
+ * Gets the IP ECN value to match.
+ *
+ * @return the IP ECN value to match (2 bits)
+ */
+ public byte ipEcn() {
+ return ipEcn;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("ipEcn", Long.toHexString(ipEcn)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), ipEcn);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPEcnCriterion) {
+ IPEcnCriterion that = (IPEcnCriterion) obj;
+ return Objects.equals(ipEcn, that.ipEcn) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPProtocolCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPProtocolCriterion.java
new file mode 100644
index 00000000..6879f802
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPProtocolCriterion.java
@@ -0,0 +1,76 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of Internet Protocol Number criterion (8 bits unsigned)
+ * integer.
+ */
+public final class IPProtocolCriterion implements Criterion {
+ private static final short MASK = 0xff;
+ private final short proto; // IP protocol number: 8 bits
+
+ /**
+ * Constructor.
+ *
+ * @param protocol the IP protocol (e.g., TCP=6, UDP=17) to match
+ * (8 bits unsigned integer)
+ */
+ IPProtocolCriterion(short protocol) {
+ this.proto = (short) (protocol & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.IP_PROTO;
+ }
+
+ /**
+ * Gets the IP protocol to match.
+ *
+ * @return the IP protocol to match (8 bits unsigned integer)
+ */
+ public short protocol() {
+ return proto;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("protocol", proto).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), proto);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPProtocolCriterion) {
+ IPProtocolCriterion that = (IPProtocolCriterion) obj;
+ return Objects.equals(proto, that.proto);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6ExthdrFlagsCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6ExthdrFlagsCriterion.java
new file mode 100644
index 00000000..2463bf64
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6ExthdrFlagsCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IPv6 Extension Header pseudo-field criterion
+ * (16 bits). Those are defined in Criterion.IPv6ExthdrFlags.
+ */
+public final class IPv6ExthdrFlagsCriterion implements Criterion {
+ private static final int MASK = 0xffff;
+ private final int exthdrFlags; // IPv6 Exthdr flags: 16 bits
+
+ /**
+ * Constructor.
+ *
+ * @param exthdrFlags the IPv6 Extension Header pseudo-field flags
+ * to match (16 bits). Those are defined in Criterion.IPv6ExthdrFlags
+ */
+ IPv6ExthdrFlagsCriterion(int exthdrFlags) {
+ this.exthdrFlags = exthdrFlags & MASK;
+ }
+
+ @Override
+ public Type type() {
+ return Type.IPV6_EXTHDR;
+ }
+
+ /**
+ * Gets the IPv6 Extension Header pseudo-field flags to match.
+ *
+ * @return the IPv6 Extension Header pseudo-field flags to match
+ * (16 bits). Those are defined in Criterion.IPv6ExthdrFlags
+ */
+ public int exthdrFlags() {
+ return exthdrFlags;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("exthdrFlags", Long.toHexString(exthdrFlags)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), exthdrFlags);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPv6ExthdrFlagsCriterion) {
+ IPv6ExthdrFlagsCriterion that = (IPv6ExthdrFlagsCriterion) obj;
+ return Objects.equals(exthdrFlags, that.exthdrFlags) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6FlowLabelCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6FlowLabelCriterion.java
new file mode 100644
index 00000000..6e1021d9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6FlowLabelCriterion.java
@@ -0,0 +1,76 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IPv6 Flow Label (RFC 6437) criterion (20 bits unsigned
+ * integer).
+ */
+public final class IPv6FlowLabelCriterion implements Criterion {
+ private static final int MASK = 0xfffff;
+ private final int flowLabel; // IPv6 flow label: 20 bits
+
+ /**
+ * Constructor.
+ *
+ * @param flowLabel the IPv6 flow label to match (20 bits)
+ */
+ IPv6FlowLabelCriterion(int flowLabel) {
+ this.flowLabel = flowLabel & MASK;
+ }
+
+ @Override
+ public Type type() {
+ return Type.IPV6_FLABEL;
+ }
+
+ /**
+ * Gets the IPv6 flow label to match.
+ *
+ * @return the IPv6 flow label to match (20 bits)
+ */
+ public int flowLabel() {
+ return flowLabel;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("flowLabel", Long.toHexString(flowLabel)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), flowLabel);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPv6FlowLabelCriterion) {
+ IPv6FlowLabelCriterion that = (IPv6FlowLabelCriterion) obj;
+ return Objects.equals(flowLabel, that.flowLabel) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDLinkLayerAddressCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDLinkLayerAddressCriterion.java
new file mode 100644
index 00000000..9f310d4a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDLinkLayerAddressCriterion.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.net.flow.criteria;
+
+import org.onlab.packet.MacAddress;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IPv6 Neighbor Discovery link-layer address criterion.
+ */
+public final class IPv6NDLinkLayerAddressCriterion implements Criterion {
+ private final MacAddress mac;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param mac the source or destination link-layer address to match
+ * @param type the match type. Should be either Type.IPV6_ND_SLL or
+ * Type.IPV6_ND_TLL
+ */
+ IPv6NDLinkLayerAddressCriterion(MacAddress mac, Type type) {
+ this.mac = mac;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the MAC link-layer address to match.
+ *
+ * @return the MAC link-layer address to match
+ */
+ public MacAddress mac() {
+ return this.mac;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("mac", mac).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), mac);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPv6NDLinkLayerAddressCriterion) {
+ IPv6NDLinkLayerAddressCriterion that =
+ (IPv6NDLinkLayerAddressCriterion) obj;
+ return Objects.equals(mac, that.mac) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDTargetAddressCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDTargetAddressCriterion.java
new file mode 100644
index 00000000..ffef044a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IPv6NDTargetAddressCriterion.java
@@ -0,0 +1,77 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.Ip6Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of IPv6 Neighbor Discovery target address criterion.
+ */
+public final class IPv6NDTargetAddressCriterion implements Criterion {
+ private final Ip6Address targetAddress;
+
+ /**
+ * Constructor.
+ *
+ * @param targetAddress the IPv6 target address to match
+ */
+ IPv6NDTargetAddressCriterion(Ip6Address targetAddress) {
+ this.targetAddress = targetAddress;
+ }
+
+ @Override
+ public Type type() {
+ return Type.IPV6_ND_TARGET;
+ }
+
+ /**
+ * Gets the IPv6 target address to match.
+ *
+ * @return the IPv6 target address to match
+ */
+ public Ip6Address targetAddress() {
+ return this.targetAddress;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("targetAddress", targetAddress).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), targetAddress);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IPv6NDTargetAddressCriterion) {
+ IPv6NDTargetAddressCriterion that =
+ (IPv6NDTargetAddressCriterion) obj;
+ return Objects.equals(targetAddress, that.targetAddress) &&
+ Objects.equals(type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpCodeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpCodeCriterion.java
new file mode 100644
index 00000000..516f61b3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpCodeCriterion.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of ICMP code criterion (8 bits unsigned integer).
+ */
+public final class IcmpCodeCriterion implements Criterion {
+ private static final short MASK = 0xff;
+ private final short icmpCode; // The ICMP code: 8 bits
+
+ /**
+ * Constructor.
+ *
+ * @param icmpCode the ICMP code to match (8 bits unsigned integer)
+ */
+ IcmpCodeCriterion(short icmpCode) {
+ this.icmpCode = (short) (icmpCode & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.ICMPV4_CODE;
+ }
+
+ /**
+ * Gets the ICMP code to match.
+ *
+ * @return the ICMP code to match (8 bits unsigned integer)
+ */
+ public short icmpCode() {
+ return icmpCode;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("icmpCode", icmpCode).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), icmpCode);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IcmpCodeCriterion) {
+ IcmpCodeCriterion that = (IcmpCodeCriterion) obj;
+ return Objects.equals(icmpCode, that.icmpCode) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpTypeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpTypeCriterion.java
new file mode 100644
index 00000000..63251425
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IcmpTypeCriterion.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of ICMP type criterion (8 bits unsigned integer).
+ */
+public final class IcmpTypeCriterion implements Criterion {
+ private static final short MASK = 0xff;
+ private final short icmpType; // The ICMP type: 8 bits
+
+ /**
+ * Constructor.
+ *
+ * @param icmpType the ICMP type to match (8 bits unsigned integer)
+ */
+ IcmpTypeCriterion(short icmpType) {
+ this.icmpType = (short) (icmpType & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.ICMPV4_TYPE;
+ }
+
+ /**
+ * Gets the ICMP type to match.
+ *
+ * @return the ICMP type to match (8 bits unsigned integer)
+ */
+ public short icmpType() {
+ return icmpType;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("icmpType", icmpType).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), icmpType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof IcmpTypeCriterion) {
+ IcmpTypeCriterion that = (IcmpTypeCriterion) obj;
+ return Objects.equals(icmpType, that.icmpType) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6CodeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6CodeCriterion.java
new file mode 100644
index 00000000..a41b6fef
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6CodeCriterion.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of ICMPv6 code criterion (8 bits unsigned integer).
+ */
+public final class Icmpv6CodeCriterion implements Criterion {
+ private static final short MASK = 0xff;
+ private final short icmpv6Code; // ICMPv6 code: 8 bits
+
+ /**
+ * Constructor.
+ *
+ * @param icmpv6Code the ICMPv6 code to match (8 bits unsigned integer)
+ */
+ Icmpv6CodeCriterion(short icmpv6Code) {
+ this.icmpv6Code = (short) (icmpv6Code & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.ICMPV6_CODE;
+ }
+
+ /**
+ * Gets the ICMPv6 code to match.
+ *
+ * @return the ICMPv6 code to match (8 bits unsigned integer)
+ */
+ public short icmpv6Code() {
+ return icmpv6Code;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("icmpv6Code", icmpv6Code).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), icmpv6Code);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Icmpv6CodeCriterion) {
+ Icmpv6CodeCriterion that = (Icmpv6CodeCriterion) obj;
+ return Objects.equals(icmpv6Code, that.icmpv6Code) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6TypeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6TypeCriterion.java
new file mode 100644
index 00000000..7410ba17
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/Icmpv6TypeCriterion.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of ICMPv6 type criterion (8 bits unsigned integer).
+ */
+public final class Icmpv6TypeCriterion implements Criterion {
+ private static final short MASK = 0xff;
+ private final short icmpv6Type; // ICMPv6 type: 8 bits
+
+ /**
+ * Constructor.
+ *
+ * @param icmpv6Type the ICMPv6 type to match (8 bits unsigned integer)
+ */
+ Icmpv6TypeCriterion(short icmpv6Type) {
+ this.icmpv6Type = (short) (icmpv6Type & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.ICMPV6_TYPE;
+ }
+
+ /**
+ * Gets the ICMPv6 type to match.
+ *
+ * @return the ICMPv6 type to match (8 bits unsigned integer)
+ */
+ public short icmpv6Type() {
+ return icmpv6Type;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("icmpv6Type", icmpv6Type).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), icmpv6Type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Icmpv6TypeCriterion) {
+ Icmpv6TypeCriterion that = (Icmpv6TypeCriterion) obj;
+ return Objects.equals(icmpv6Type, that.icmpv6Type) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java
new file mode 100644
index 00000000..88a6fe10
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.IndexedLambda;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of indexed lambda criterion.
+ */
+public class IndexedLambdaCriterion implements Criterion {
+
+ private final IndexedLambda lambda;
+
+ /**
+ * Creates a criterion with the specified value.
+ *
+ * @param lambda lambda index number
+ */
+ IndexedLambdaCriterion(IndexedLambda lambda) {
+ this.lambda = checkNotNull(lambda);
+ }
+
+ @Override
+ public Type type() {
+ // TODO: consider defining a new specific type
+ // Now OCH_SIGID is used due to compatibility concerns
+ return Type.OCH_SIGID;
+ }
+
+ /**
+ * Returns the indexed lambda to match.
+ *
+ * @return the indexed lambda to match
+ */
+ public IndexedLambda lambda() {
+ return lambda;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof IndexedLambdaCriterion)) {
+ return false;
+ }
+ final IndexedLambdaCriterion that = (IndexedLambdaCriterion) obj;
+ return Objects.equals(this.lambda, that.lambda);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lambda", lambda)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/LambdaCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/LambdaCriterion.java
new file mode 100644
index 00000000..656800b1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/LambdaCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of lambda (wavelength) criterion (16 bits unsigned
+ * integer).
+ */
+public final class LambdaCriterion implements Criterion {
+ private static final int MASK = 0xffff;
+ private final int lambda; // Lambda value: 16 bits
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param lambda the lambda (wavelength) to match (16 bits unsigned
+ * integer)
+ * @param type the match type. Should be Type.OCH_SIGID
+ */
+ LambdaCriterion(int lambda, Type type) {
+ this.lambda = lambda & MASK;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the lambda (wavelength) to match.
+ *
+ * @return the lambda (wavelength) to match (16 bits unsigned integer)
+ */
+ public int lambda() {
+ return lambda;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("lambda", lambda).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof LambdaCriterion) {
+ LambdaCriterion that = (LambdaCriterion) obj;
+ return Objects.equals(lambda, that.lambda) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MetadataCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MetadataCriterion.java
new file mode 100644
index 00000000..8177b483
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MetadataCriterion.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of Metadata criterion.
+ */
+public final class MetadataCriterion implements Criterion {
+ private final long metadata;
+
+ /**
+ * Constructor.
+ *
+ * @param metadata the metadata to match (64 bits data)
+ */
+ MetadataCriterion(long metadata) {
+ this.metadata = metadata;
+ }
+
+ @Override
+ public Type type() {
+ return Type.METADATA;
+ }
+
+ /**
+ * Gets the metadata to match.
+ *
+ * @return the metadata to match (64 bits data)
+ */
+ public long metadata() {
+ return metadata;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("metadata", Long.toHexString(metadata))
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), metadata);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MetadataCriterion) {
+ MetadataCriterion that = (MetadataCriterion) obj;
+ return Objects.equals(metadata, that.metadata) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsBosCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsBosCriterion.java
new file mode 100644
index 00000000..1ace8931
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsBosCriterion.java
@@ -0,0 +1,48 @@
+package org.onosproject.net.flow.criteria;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import java.util.Objects;
+
+/**
+ * Implementation of MPLS BOS criterion (1 bit).
+ */
+public class MplsBosCriterion implements Criterion {
+ private boolean mplsBos;
+
+ MplsBosCriterion(boolean mplsBos) {
+ this.mplsBos = mplsBos;
+ }
+
+ @Override
+ public Type type() {
+ return Type.MPLS_BOS;
+ }
+
+ public boolean mplsBos() {
+ return mplsBos;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("bos", mplsBos).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), mplsBos);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MplsBosCriterion) {
+ MplsBosCriterion that = (MplsBosCriterion) obj;
+ return Objects.equals(mplsBos, that.mplsBos()) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsCriterion.java
new file mode 100644
index 00000000..34d384f1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/MplsCriterion.java
@@ -0,0 +1,67 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.MplsLabel;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of MPLS tag criterion (20 bits).
+ */
+public final class MplsCriterion implements Criterion {
+ private static final int MASK = 0xfffff;
+ private final MplsLabel mplsLabel;
+
+ MplsCriterion(MplsLabel mplsLabel) {
+ this.mplsLabel = mplsLabel;
+ }
+
+ @Override
+ public Type type() {
+ return Type.MPLS_LABEL;
+ }
+
+ public MplsLabel label() {
+ return mplsLabel;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("mpls", mplsLabel).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), mplsLabel);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MplsCriterion) {
+ MplsCriterion that = (MplsCriterion) obj;
+ return Objects.equals(mplsLabel, that.mplsLabel) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalCriterion.java
new file mode 100644
index 00000000..3a51a6bd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalCriterion.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.net.flow.criteria;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.OchSignal;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of OCh (Optical Channel) signal criterion.
+ * This criterion is based on the specification of "OFPXMT_EXP_OCH_SIGID" in
+ * Open Networking Foundation "Optical Transport Protocol Extension Version 1.0", but
+ * defined in protocol agnostic way.
+ */
+public final class OchSignalCriterion implements Criterion {
+
+ private final OchSignal lambda;
+
+ /**
+ * Create an instance with the specified OCh signal.
+ *
+ * @param lambda OCh signal
+ */
+ OchSignalCriterion(OchSignal lambda) {
+ this.lambda = checkNotNull(lambda);
+ }
+
+ @Override
+ public Type type() {
+ return Type.OCH_SIGID;
+ }
+
+ /**
+ * Returns the OCh signal to match.
+ *
+ * @return the OCh signal to match
+ */
+ public OchSignal lambda() {
+ return lambda;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof OchSignalCriterion)) {
+ return false;
+ }
+ final OchSignalCriterion that = (OchSignalCriterion) obj;
+ return Objects.equals(this.lambda, that.lambda);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lambda", lambda)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalTypeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalTypeCriterion.java
new file mode 100644
index 00000000..cf838bf3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OchSignalTypeCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.flow.criteria;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.OchSignalType;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of OCh (Optical Channel) signal type criterion.
+ */
+public class OchSignalTypeCriterion implements Criterion {
+
+ private final OchSignalType signalType;
+
+ /**
+ * Creates a criterion with the specified value.
+ *
+ * @param signalType OCh signal type
+ */
+ OchSignalTypeCriterion(OchSignalType signalType) {
+ this.signalType = checkNotNull(signalType);
+ }
+
+ @Override
+ public Type type() {
+ return Type.OCH_SIGTYPE;
+ }
+
+ /**
+ * Returns the OCh signal type to match.
+ *
+ * @return the OCh signal type to match
+ */
+ public OchSignalType signalType() {
+ return signalType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), signalType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof OchSignalTypeCriterion)) {
+ return false;
+ }
+ final OchSignalTypeCriterion that = (OchSignalTypeCriterion) obj;
+ return Objects.equals(this.signalType, that.signalType);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("signalType", signalType)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OpticalSignalTypeCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OpticalSignalTypeCriterion.java
new file mode 100644
index 00000000..b712675b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/OpticalSignalTypeCriterion.java
@@ -0,0 +1,83 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of optical signal type criterion (8 bits unsigned
+ * integer).
+ *
+ * @deprecated in Cardinal Release
+ */
+@Deprecated
+public final class OpticalSignalTypeCriterion implements Criterion {
+ private static final short MASK = 0xff;
+ private final short signalType; // Signal type value: 8 bits
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param signalType the optical signal type to match (8 bits unsigned
+ * integer)
+ * @param type the match type. Should be Type.OCH_SIGTYPE
+ */
+ OpticalSignalTypeCriterion(short signalType, Type type) {
+ this.signalType = (short) (signalType & MASK);
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the optical signal type to match.
+ *
+ * @return the optical signal type to match (8 bits unsigned integer)
+ */
+ public short signalType() {
+ return signalType;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("signalType", signalType).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), signalType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OpticalSignalTypeCriterion) {
+ OpticalSignalTypeCriterion that = (OpticalSignalTypeCriterion) obj;
+ return Objects.equals(signalType, that.signalType) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/PortCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/PortCriterion.java
new file mode 100644
index 00000000..f07cd500
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/PortCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import org.onosproject.net.PortNumber;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of input port criterion.
+ */
+public final class PortCriterion implements Criterion {
+ private final PortNumber port;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param port the input port number to match
+ * @param type the match type. Should be either Type.IN_PORT or
+ * Type.IN_PHY_PORT
+ */
+ PortCriterion(PortNumber port, Type type) {
+ this.port = port;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the input port number to match.
+ *
+ * @return the input port number to match
+ */
+ public PortNumber port() {
+ return this.port;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("port", port).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), port);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PortCriterion) {
+ PortCriterion that = (PortCriterion) obj;
+ return Objects.equals(port, that.port) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/SctpPortCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/SctpPortCriterion.java
new file mode 100644
index 00000000..2d4cf15a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/SctpPortCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.TpPort;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of SCTP port criterion (16 bits unsigned integer).
+ */
+public final class SctpPortCriterion implements Criterion {
+ private final TpPort sctpPort;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param sctpPort the SCTP port to match
+ * @param type the match type. Should be either Type.SCTP_SRC or
+ * Type.SCTP_DST
+ */
+ SctpPortCriterion(TpPort sctpPort, Type type) {
+ this.sctpPort = sctpPort;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the SCTP port to match.
+ *
+ * @return the SCTP port to match
+ */
+ public TpPort sctpPort() {
+ return this.sctpPort;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("sctpPort", sctpPort).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), sctpPort);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof SctpPortCriterion) {
+ SctpPortCriterion that = (SctpPortCriterion) obj;
+ return Objects.equals(sctpPort, that.sctpPort) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TcpPortCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TcpPortCriterion.java
new file mode 100644
index 00000000..e682b238
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TcpPortCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.TpPort;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of TCP port criterion (16 bits unsigned integer).
+ */
+public final class TcpPortCriterion implements Criterion {
+ private final TpPort tcpPort;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param tcpPort the TCP port to match
+ * @param type the match type. Should be either Type.TCP_SRC or
+ * Type.TCP_DST
+ */
+ TcpPortCriterion(TpPort tcpPort, Type type) {
+ this.tcpPort = tcpPort;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the TCP port to match.
+ *
+ * @return the TCP port to match
+ */
+ public TpPort tcpPort() {
+ return this.tcpPort;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("tcpPort", tcpPort).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), tcpPort);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof TcpPortCriterion) {
+ TcpPortCriterion that = (TcpPortCriterion) obj;
+ return Objects.equals(tcpPort, that.tcpPort) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TunnelIdCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TunnelIdCriterion.java
new file mode 100644
index 00000000..3362c73f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/TunnelIdCriterion.java
@@ -0,0 +1,74 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+/**
+ * Implementation of Tunnel ID criterion.
+ */
+public class TunnelIdCriterion implements Criterion {
+ private final long tunnelId;
+
+ /**
+ * Constructor.
+ *
+ * @param tunnelId a Tunnel ID to match(64 bits)
+ */
+ TunnelIdCriterion(long tunnelId) {
+ this.tunnelId = tunnelId;
+ }
+
+ @Override
+ public Type type() {
+ return Type.TUNNEL_ID;
+ }
+
+ /**
+ * Gets the Tunnel ID to match.
+ *
+ * @return the Tunnel ID to match (64 bits)
+ */
+ public long tunnelId() {
+ return tunnelId;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("tunnelId", Long.toHexString(tunnelId))
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), tunnelId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof TunnelIdCriterion) {
+ TunnelIdCriterion that = (TunnelIdCriterion) obj;
+ return Objects.equals(tunnelId, that.tunnelId) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/UdpPortCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/UdpPortCriterion.java
new file mode 100644
index 00000000..8a29f471
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/UdpPortCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.TpPort;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of UDP port criterion (16 bits unsigned integer).
+ */
+public final class UdpPortCriterion implements Criterion {
+ private final TpPort udpPort;
+ private final Type type;
+
+ /**
+ * Constructor.
+ *
+ * @param udpPort the UDP port to match
+ * @param type the match type. Should be either Type.UDP_SRC or
+ * Type.UDP_DST
+ */
+ UdpPortCriterion(TpPort udpPort, Type type) {
+ this.udpPort = udpPort;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ /**
+ * Gets the UDP port to match.
+ *
+ * @return the UDP port to match
+ */
+ public TpPort udpPort() {
+ return this.udpPort;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("udpPort", udpPort).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), udpPort);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof UdpPortCriterion) {
+ UdpPortCriterion that = (UdpPortCriterion) obj;
+ return Objects.equals(udpPort, that.udpPort) &&
+ Objects.equals(type, that.type);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanIdCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanIdCriterion.java
new file mode 100644
index 00000000..c73edb12
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanIdCriterion.java
@@ -0,0 +1,76 @@
+/*
+ * 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.flow.criteria;
+
+import org.onlab.packet.VlanId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of VLAN ID criterion.
+ */
+public final class VlanIdCriterion implements Criterion {
+ private final VlanId vlanId;
+
+ /**
+ * Constructor.
+ *
+ * @param vlanId the VLAN ID to match
+ */
+ VlanIdCriterion(VlanId vlanId) {
+ this.vlanId = vlanId;
+ }
+
+ @Override
+ public Type type() {
+ return Type.VLAN_VID;
+ }
+
+ /**
+ * Gets the VLAN ID to match.
+ *
+ * @return the VLAN ID to match
+ */
+ public VlanId vlanId() {
+ return vlanId;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("vlanId", vlanId).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), vlanId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof VlanIdCriterion) {
+ VlanIdCriterion that = (VlanIdCriterion) obj;
+ return Objects.equals(vlanId, that.vlanId) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanPcpCriterion.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanPcpCriterion.java
new file mode 100644
index 00000000..0c83e141
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/VlanPcpCriterion.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow.criteria;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of VLAN priority criterion (3 bits).
+ */
+public final class VlanPcpCriterion implements Criterion {
+ private static final byte MASK = 0x7;
+ private final byte vlanPcp; // VLAN pcp value: 3 bits
+
+ /**
+ * Constructor.
+ *
+ * @param vlanPcp the VLAN priority to match (3 bits)
+ */
+ VlanPcpCriterion(byte vlanPcp) {
+ this.vlanPcp = (byte) (vlanPcp & MASK);
+ }
+
+ @Override
+ public Type type() {
+ return Type.VLAN_PCP;
+ }
+
+ /**
+ * Gets the VLAN priority to match.
+ *
+ * @return the VLAN priority to match (3 bits)
+ */
+ public byte priority() {
+ return vlanPcp;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("priority", Long.toHexString(vlanPcp)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), vlanPcp);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof VlanPcpCriterion) {
+ VlanPcpCriterion that = (VlanPcpCriterion) obj;
+ return Objects.equals(vlanPcp, that.vlanPcp) &&
+ Objects.equals(this.type(), that.type());
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/package-info.java
new file mode 100644
index 00000000..1f091928
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/criteria/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.
+ */
+
+/**
+ * Traffic selection criteria model.
+ */
+package org.onosproject.net.flow.criteria;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instruction.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instruction.java
new file mode 100644
index 00000000..6f2cac6b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instruction.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.flow.instructions;
+
+/**
+ * Abstraction of a single traffic treatment step.
+ */
+public interface Instruction {
+
+ /**
+ * Represents the type of traffic treatment.
+ */
+ enum Type {
+ /**
+ * Signifies that the traffic should be dropped.
+ */
+ DROP,
+
+ /**
+ * Signifies that the traffic should be output to a port.
+ */
+ OUTPUT,
+
+ /**
+ * Signifies that traffic should be sent out of a group.
+ */
+ GROUP,
+
+ /**
+ * Signifies that traffic should be metered according to a meter.
+ */
+ METER,
+
+ /**
+ * Signifies that the traffic should be modified in L0 way.
+ */
+ L0MODIFICATION,
+
+ /**
+ * Signifies that the traffic should be modified in L2 way.
+ */
+ L2MODIFICATION,
+
+ /**
+ * Signifies that the traffic should be passed to another table.
+ */
+ TABLE,
+
+ /**
+ * Signifies that the traffic should be modified in L3 way.
+ */
+ L3MODIFICATION,
+
+ /**
+ * Signifies that metadata be attached to traffic.
+ */
+ METADATA,
+
+ /**
+ * Signifies that the traffic should be modified in L4 way.
+ */
+ L4MODIFICATION
+ }
+
+ /**
+ * Returns the type of instruction.
+ * @return type of instruction
+ */
+ Type type();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java
new file mode 100644
index 00000000..c5358a29
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java
@@ -0,0 +1,742 @@
+/*
+ * 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.instructions;
+
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.L0ModificationInstruction.L0SubType;
+import org.onosproject.net.flow.instructions.L0ModificationInstruction.ModLambdaInstruction;
+import org.onosproject.net.flow.instructions.L0ModificationInstruction.ModOchSignalInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction.L3SubType;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPv6FlowLabelInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModTtlInstruction;
+import org.onosproject.net.flow.instructions.L4ModificationInstruction.L4SubType;
+import org.onosproject.net.flow.instructions.L4ModificationInstruction.ModTransportPortInstruction;
+import org.onosproject.net.meter.MeterId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Factory class for creating various traffic treatment instructions.
+ */
+public final class Instructions {
+
+
+ // Ban construction
+ private Instructions() {}
+
+ /**
+ * Creates an output instruction using the specified port number. This can
+ * include logical ports such as CONTROLLER, FLOOD, etc.
+ *
+ * @param number port number
+ * @return output instruction
+ */
+ public static OutputInstruction createOutput(final PortNumber number) {
+ checkNotNull(number, "PortNumber cannot be null");
+ return new OutputInstruction(number);
+ }
+
+ /**
+ * Creates a drop instruction.
+ *
+ * @return drop instruction
+ */
+ public static DropInstruction createDrop() {
+ return new DropInstruction();
+ }
+
+ /**
+ * Creates a group instruction.
+ *
+ * @param groupId Group Id
+ * @return group instruction
+ */
+ public static GroupInstruction createGroup(final GroupId groupId) {
+ checkNotNull(groupId, "GroupId cannot be null");
+ return new GroupInstruction(groupId);
+ }
+
+ public static MeterInstruction meterTraffic(final MeterId meterId) {
+ checkNotNull(meterId, "meter id cannot be null");
+ return new MeterInstruction(meterId);
+ }
+
+ /**
+ * Creates a l0 modification.
+ *
+ * @param lambda the lambda to modify to
+ * @return a l0 modification
+ * @deprecated in Cardinal Release. Use {@link #modL0Lambda(Lambda)} instead.
+ */
+ @Deprecated
+ public static L0ModificationInstruction modL0Lambda(short lambda) {
+ checkNotNull(lambda, "L0 lambda cannot be null");
+ return new ModLambdaInstruction(L0SubType.LAMBDA, lambda);
+ }
+
+ /**
+ * Creates an L0 modification with the specified OCh signal.
+ *
+ * @param lambda OCh signal
+ * @return an L0 modification
+ */
+ public static L0ModificationInstruction modL0Lambda(Lambda lambda) {
+ checkNotNull(lambda, "L0 OCh signal cannot be null");
+
+ if (lambda instanceof IndexedLambda) {
+ return new ModLambdaInstruction(L0SubType.LAMBDA, (short) ((IndexedLambda) lambda).index());
+ } else if (lambda instanceof OchSignal) {
+ return new ModOchSignalInstruction((OchSignal) lambda);
+ } else {
+ throw new UnsupportedOperationException(String.format("Unsupported type: %s", lambda));
+ }
+ }
+
+ /**
+ * Creates a l2 src modification.
+ *
+ * @param addr the mac address to modify to
+ * @return a l2 modification
+ */
+ public static L2ModificationInstruction modL2Src(MacAddress addr) {
+ checkNotNull(addr, "Src l2 address cannot be null");
+ return new L2ModificationInstruction.ModEtherInstruction(
+ L2ModificationInstruction.L2SubType.ETH_SRC, addr);
+ }
+
+ /**
+ * Creates a L2 dst modification.
+ *
+ * @param addr the mac address to modify to
+ * @return a L2 modification
+ */
+ public static L2ModificationInstruction modL2Dst(MacAddress addr) {
+ checkNotNull(addr, "Dst l2 address cannot be null");
+ return new L2ModificationInstruction.ModEtherInstruction(
+ L2ModificationInstruction.L2SubType.ETH_DST, addr);
+ }
+
+ /**
+ * Creates a VLAN ID modification.
+ *
+ * @param vlanId the VLAN ID to modify to
+ * @return a L2 modification
+ */
+ public static L2ModificationInstruction modVlanId(VlanId vlanId) {
+ checkNotNull(vlanId, "VLAN id cannot be null");
+ return new L2ModificationInstruction.ModVlanIdInstruction(vlanId);
+ }
+
+ /**
+ * Creates a VLAN PCP modification.
+ *
+ * @param vlanPcp the PCP to modify to
+ * @return a L2 modification
+ */
+ public static L2ModificationInstruction modVlanPcp(Byte vlanPcp) {
+ checkNotNull(vlanPcp, "VLAN Pcp cannot be null");
+ return new L2ModificationInstruction.ModVlanPcpInstruction(vlanPcp);
+ }
+
+ /**
+ * Creates a MPLS label modification.
+ *
+ * @param mplsLabel MPLS label to set
+ * @return a L2 Modification
+ */
+ public static L2ModificationInstruction modMplsLabel(MplsLabel mplsLabel) {
+ checkNotNull(mplsLabel, "MPLS label cannot be null");
+ return new L2ModificationInstruction.ModMplsLabelInstruction(mplsLabel);
+ }
+
+ /**
+ * Creates a MPLS BOS bit modification.
+ *
+ * @param mplsBos MPLS BOS bit to set (true) or unset (false)
+ * @return a L2 Modification
+ */
+ public static L2ModificationInstruction modMplsBos(boolean mplsBos) {
+ return new L2ModificationInstruction.ModMplsBosInstruction(mplsBos);
+ }
+
+ /**
+ * Creates a MPLS decrement TTL modification.
+ *
+ * @return a L2 Modification
+ */
+ public static L2ModificationInstruction decMplsTtl() {
+ return new L2ModificationInstruction.ModMplsTtlInstruction();
+ }
+
+ /**
+ * Creates a L3 IPv4 src modification.
+ *
+ * @param addr the IPv4 address to modify to
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction modL3Src(IpAddress addr) {
+ checkNotNull(addr, "Src l3 IPv4 address cannot be null");
+ return new ModIPInstruction(L3SubType.IPV4_SRC, addr);
+ }
+
+ /**
+ * Creates a L3 IPv4 dst modification.
+ *
+ * @param addr the IPv4 address to modify to
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction modL3Dst(IpAddress addr) {
+ checkNotNull(addr, "Dst l3 IPv4 address cannot be null");
+ return new ModIPInstruction(L3SubType.IPV4_DST, addr);
+ }
+
+ /**
+ * Creates a L3 IPv6 src modification.
+ *
+ * @param addr the IPv6 address to modify to
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction modL3IPv6Src(IpAddress addr) {
+ checkNotNull(addr, "Src l3 IPv6 address cannot be null");
+ return new ModIPInstruction(L3SubType.IPV6_SRC, addr);
+ }
+
+ /**
+ * Creates a L3 IPv6 dst modification.
+ *
+ * @param addr the IPv6 address to modify to
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction modL3IPv6Dst(IpAddress addr) {
+ checkNotNull(addr, "Dst l3 IPv6 address cannot be null");
+ return new ModIPInstruction(L3SubType.IPV6_DST, addr);
+ }
+
+ /**
+ * Creates a L3 IPv6 Flow Label modification.
+ *
+ * @param flowLabel the IPv6 flow label to modify to (20 bits)
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction modL3IPv6FlowLabel(int flowLabel) {
+ return new ModIPv6FlowLabelInstruction(flowLabel);
+ }
+
+ /**
+ * Creates a L3 decrement TTL modification.
+ *
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction decNwTtl() {
+ return new ModTtlInstruction(L3SubType.DEC_TTL);
+ }
+
+ /**
+ * Creates a L3 copy TTL to outer header modification.
+ *
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction copyTtlOut() {
+ return new ModTtlInstruction(L3SubType.TTL_OUT);
+ }
+
+ /**
+ * Creates a L3 copy TTL to inner header modification.
+ *
+ * @return a L3 modification
+ */
+ public static L3ModificationInstruction copyTtlIn() {
+ return new ModTtlInstruction(L3SubType.TTL_IN);
+ }
+
+ /**
+ * Creates a push MPLS header instruction.
+ *
+ * @return a L2 modification.
+ */
+ public static Instruction pushMpls() {
+ return new L2ModificationInstruction.PushHeaderInstructions(
+ L2ModificationInstruction.L2SubType.MPLS_PUSH,
+ EthType.EtherType.MPLS_UNICAST.ethType());
+ }
+
+ /**
+ * Creates a pop MPLS header instruction.
+ *
+ * @return a L2 modification.
+ */
+ public static Instruction popMpls() {
+ return new L2ModificationInstruction.PushHeaderInstructions(
+ L2ModificationInstruction.L2SubType.MPLS_POP,
+ EthType.EtherType.MPLS_UNICAST.ethType());
+ }
+
+ /**
+ * Creates a pop MPLS header instruction with a particular ethertype.
+ *
+ * @param etherType Ethernet type to set
+ * @return a L2 modification.
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ public static Instruction popMpls(int etherType) {
+ checkNotNull(etherType, "Ethernet type cannot be null");
+ return new L2ModificationInstruction.PushHeaderInstructions(
+ L2ModificationInstruction.L2SubType.MPLS_POP, new EthType(etherType));
+ }
+
+
+ /**
+ * Creates a pop MPLS header instruction with a particular ethertype.
+ *
+ * @param etherType Ethernet type to set
+ * @return a L2 modification.
+ */
+ public static Instruction popMpls(EthType etherType) {
+ checkNotNull(etherType, "Ethernet type cannot be null");
+ return new L2ModificationInstruction.PushHeaderInstructions(
+ L2ModificationInstruction.L2SubType.MPLS_POP, etherType);
+ }
+
+ /**
+ * Creates a pop VLAN header instruction.
+ *
+ * @return a L2 modification
+ */
+ public static Instruction popVlan() {
+ return new L2ModificationInstruction.PopVlanInstruction(
+ L2ModificationInstruction.L2SubType.VLAN_POP);
+ }
+
+ /**
+ * Creates a push VLAN header instruction.
+ *
+ * @return a L2 modification
+ */
+ public static Instruction pushVlan() {
+ return new L2ModificationInstruction.PushHeaderInstructions(
+ L2ModificationInstruction.L2SubType.VLAN_PUSH,
+ EthType.EtherType.VLAN.ethType());
+ }
+
+ /**
+ * Sends the packet to the table id.
+ *
+ * @param tableId flow rule table id
+ * @return table type transition instruction
+ */
+ public static Instruction transition(Integer tableId) {
+ checkNotNull(tableId, "Table id cannot be null");
+ return new TableTypeTransition(tableId);
+ }
+
+ /**
+ * Writes metadata to associate with a packet.
+ *
+ * @param metadata the metadata value to write
+ * @param metadataMask the bits to mask for the metadata value
+ * @return metadata instruction
+ */
+ public static Instruction writeMetadata(long metadata, long metadataMask) {
+ return new MetadataInstruction(metadata, metadataMask);
+ }
+
+ /**
+ * Creates a Tunnel ID modification.
+ *
+ * @param tunnelId the Tunnel ID to modify to
+ * @return a L2 modification
+ */
+ public static L2ModificationInstruction modTunnelId(long tunnelId) {
+ checkNotNull(tunnelId, "Tunnel id cannot be null");
+ return new L2ModificationInstruction.ModTunnelIdInstruction(tunnelId);
+ }
+
+ /**
+ * Creates a TCP src modification.
+ *
+ * @param port the TCP port number to modify to
+ * @return a L4 modification
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static L4ModificationInstruction modTcpSrc(short port) {
+ checkNotNull(port, "Src TCP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.TCP_SRC, TpPort.tpPort(port));
+ }
+
+ /**
+ * Creates a TCP src modification.
+ *
+ * @param port the TCP port number to modify to
+ * @return a L4 modification
+ */
+ public static L4ModificationInstruction modTcpSrc(TpPort port) {
+ checkNotNull(port, "Src TCP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.TCP_SRC, port);
+ }
+
+ /**
+ * Creates a TCP dst modification.
+ *
+ * @param port the TCP port number to modify to
+ * @return a L4 modification
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static L4ModificationInstruction modTcpDst(short port) {
+ checkNotNull(port, "Dst TCP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.TCP_DST, TpPort.tpPort(port));
+ }
+
+ /**
+ * Creates a TCP dst modification.
+ *
+ * @param port the TCP port number to modify to
+ * @return a L4 modification
+ */
+ public static L4ModificationInstruction modTcpDst(TpPort port) {
+ checkNotNull(port, "Dst TCP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.TCP_DST, port);
+ }
+
+ /**
+ * Creates a UDP src modification.
+ *
+ * @param port the UDP port number to modify to
+ * @return a L4 modification
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static L4ModificationInstruction modUdpSrc(short port) {
+ checkNotNull(port, "Src UDP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.UDP_SRC, TpPort.tpPort(port));
+ }
+
+ /**
+ * Creates a UDP src modification.
+ *
+ * @param port the UDP port number to modify to
+ * @return a L4 modification
+ */
+ public static L4ModificationInstruction modUdpSrc(TpPort port) {
+ checkNotNull(port, "Src UDP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.UDP_SRC, port);
+ }
+
+ /**
+ * Creates a UDP dst modification.
+ *
+ * @param port the UDP port number to modify to
+ * @return a L4 modification
+ * @deprecated in Drake release
+ */
+ @Deprecated
+ public static L4ModificationInstruction modUdpDst(short port) {
+ checkNotNull(port, "Dst UDP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.UDP_DST, TpPort.tpPort(port));
+ }
+
+ /**
+ * Creates a UDP dst modification.
+ *
+ * @param port the UDP port number to modify to
+ * @return a L4 modification
+ */
+ public static L4ModificationInstruction modUdpDst(TpPort port) {
+ checkNotNull(port, "Dst UDP port cannot be null");
+ return new ModTransportPortInstruction(L4SubType.UDP_DST, port);
+ }
+
+ /**
+ * Drop instruction.
+ */
+ public static final class DropInstruction implements Instruction {
+
+ private DropInstruction() {}
+
+ @Override
+ public Type type() {
+ return Type.DROP;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString()).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DropInstruction) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Output Instruction.
+ */
+ public static final class OutputInstruction implements Instruction {
+ private final PortNumber port;
+
+ private OutputInstruction(PortNumber port) {
+ this.port = port;
+ }
+
+ public PortNumber port() {
+ return port;
+ }
+
+ @Override
+ public Type type() {
+ return Type.OUTPUT;
+ }
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("port", port).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), port);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OutputInstruction) {
+ OutputInstruction that = (OutputInstruction) obj;
+ return Objects.equals(port, that.port);
+
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Group Instruction.
+ */
+ public static final class GroupInstruction implements Instruction {
+ private final GroupId groupId;
+
+ private GroupInstruction(GroupId groupId) {
+ this.groupId = groupId;
+ }
+
+ public GroupId groupId() {
+ return groupId;
+ }
+
+ @Override
+ public Type type() {
+ return Type.GROUP;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("group ID", groupId.id()).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), groupId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof GroupInstruction) {
+ GroupInstruction that = (GroupInstruction) obj;
+ return Objects.equals(groupId, that.groupId);
+
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A meter instruction.
+ */
+ public static final class MeterInstruction implements Instruction {
+ private final MeterId meterId;
+
+ private MeterInstruction(MeterId meterId) {
+ this.meterId = meterId;
+ }
+
+ public MeterId meterId() {
+ return meterId;
+ }
+
+ @Override
+ public Type type() {
+ return Type.METER;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("meter ID", meterId.id()).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), meterId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MeterInstruction) {
+ MeterInstruction that = (MeterInstruction) obj;
+ return Objects.equals(meterId, that.meterId);
+
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Transition instruction.
+ */
+ public static class TableTypeTransition implements Instruction {
+ private final Integer tableId;
+
+ TableTypeTransition(Integer tableId) {
+ this.tableId = tableId;
+ }
+
+ @Override
+ public Type type() {
+ return Type.TABLE;
+ }
+
+ public Integer tableId() {
+ return this.tableId;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("tableId", this.tableId).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), tableId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof TableTypeTransition) {
+ TableTypeTransition that = (TableTypeTransition) obj;
+ return Objects.equals(tableId, that.tableId);
+
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Metadata instruction.
+ */
+ public static class MetadataInstruction implements Instruction {
+ private final long metadata;
+ private final long metadataMask;
+
+ MetadataInstruction(long metadata, long metadataMask) {
+ this.metadata = metadata;
+ this.metadataMask = metadataMask;
+ }
+
+ @Override
+ public Type type() {
+ return Type.METADATA;
+ }
+
+ public long metadata() {
+ return this.metadata;
+ }
+
+ public long metadataMask() {
+ return this.metadataMask;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(type().toString())
+ .add("metadata", Long.toHexString(this.metadata))
+ .add("metadata mask", Long.toHexString(this.metadataMask))
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type().ordinal(), metadata, metadataMask);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MetadataInstruction) {
+ MetadataInstruction that = (MetadataInstruction) obj;
+ return Objects.equals(metadata, that.metadata) &&
+ Objects.equals(metadataMask, that.metadataMask);
+
+ }
+ return false;
+ }
+ }
+
+}
+
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L0ModificationInstruction.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L0ModificationInstruction.java
new file mode 100644
index 00000000..a6e5903c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L0ModificationInstruction.java
@@ -0,0 +1,139 @@
+/*
+ * 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.instructions;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.OchSignal;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Objects;
+
+public abstract class L0ModificationInstruction implements Instruction {
+
+ /**
+ * Represents the type of traffic treatment.
+ */
+ public enum L0SubType {
+ /**
+ * Lambda modification.
+ */
+ LAMBDA,
+ /**
+ * OCh (Optical Channel) modification.
+ */
+ OCH,
+ }
+
+ public abstract L0SubType subtype();
+
+ @Override
+ public final Type type() {
+ return Type.L0MODIFICATION;
+ }
+
+ /**
+ * Represents a L0 lambda modification instruction.
+ */
+ public static final class ModLambdaInstruction extends L0ModificationInstruction {
+
+ private final L0SubType subtype;
+ private final short lambda;
+
+ ModLambdaInstruction(L0SubType subType, short lambda) {
+ this.subtype = subType;
+ this.lambda = lambda;
+ }
+
+ @Override
+ public L0SubType subtype() {
+ return this.subtype;
+ }
+
+ public short lambda() {
+ return this.lambda;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("lambda", lambda).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype, lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModLambdaInstruction) {
+ ModLambdaInstruction that = (ModLambdaInstruction) obj;
+ return Objects.equals(lambda, that.lambda) &&
+ Objects.equals(subtype, that.subtype);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents an L0 OCh (Optical Channel) modification instruction.
+ */
+ public static final class ModOchSignalInstruction extends L0ModificationInstruction {
+
+ private final OchSignal lambda;
+
+ ModOchSignalInstruction(OchSignal lambda) {
+ this.lambda = lambda;
+ }
+
+ @Override
+ public L0SubType subtype() {
+ return L0SubType.OCH;
+ }
+
+ public OchSignal lambda() {
+ return lambda;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ModOchSignalInstruction)) {
+ return false;
+ }
+ final ModOchSignalInstruction that = (ModOchSignalInstruction) obj;
+ return Objects.equals(this.lambda, that.lambda);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lambda", lambda)
+ .toString();
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L2ModificationInstruction.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L2ModificationInstruction.java
new file mode 100644
index 00000000..0dbbb451
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L2ModificationInstruction.java
@@ -0,0 +1,517 @@
+/*
+ * 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.instructions;
+
+import org.onlab.packet.EthType;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Abstraction of a single traffic treatment step.
+ */
+public abstract class L2ModificationInstruction implements Instruction {
+
+ /**
+ * Represents the type of traffic treatment.
+ */
+ public enum L2SubType {
+ /**
+ * Ether src modification.
+ */
+ ETH_SRC,
+
+ /**
+ * Ether dst modification.
+ */
+ ETH_DST,
+
+ /**
+ * VLAN id modification.
+ */
+ VLAN_ID,
+
+ /**
+ * VLAN priority modification.
+ */
+ VLAN_PCP,
+
+ /**
+ * MPLS Label modification.
+ */
+ MPLS_LABEL,
+
+ /**
+ * MPLS Push modification.
+ */
+ MPLS_PUSH,
+
+ /**
+ * MPLS Pop modification.
+ */
+ MPLS_POP,
+
+ /**
+ * MPLS TTL modification.
+ */
+ DEC_MPLS_TTL,
+
+ /**
+ * VLAN Pop modification.
+ */
+ VLAN_POP,
+
+ /**
+ * VLAN Push modification.
+ */
+ VLAN_PUSH,
+
+ /**
+ * Tunnel id modification.
+ */
+ TUNNEL_ID,
+
+ /**
+ * MPLS BOS instruction.
+ */
+ MPLS_BOS
+ }
+
+ // TODO: Create factory class 'Instructions' that will have various factory
+ // to create specific instructions.
+
+ public abstract L2SubType subtype();
+
+ @Override
+ public final Type type() {
+ return Type.L2MODIFICATION;
+ }
+
+ /**
+ * Represents a L2 src/dst modification instruction.
+ */
+ public static final class ModEtherInstruction extends L2ModificationInstruction {
+
+ private final L2SubType subtype;
+ private final MacAddress mac;
+
+ ModEtherInstruction(L2SubType subType, MacAddress addr) {
+
+ this.subtype = subType;
+ this.mac = addr;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return this.subtype;
+ }
+
+ public MacAddress mac() {
+ return this.mac;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("mac", mac).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype, mac);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModEtherInstruction) {
+ ModEtherInstruction that = (ModEtherInstruction) obj;
+ return Objects.equals(mac, that.mac) &&
+ Objects.equals(subtype, that.subtype);
+ }
+ return false;
+ }
+ }
+
+ // TODO This instruction is reused for Pop-Mpls. Consider renaming.
+ public static final class PushHeaderInstructions extends
+ L2ModificationInstruction {
+
+
+ private final L2SubType subtype;
+ private final EthType ethernetType; // Ethernet type value: 16 bits
+
+ PushHeaderInstructions(L2SubType subType, EthType ethernetType) {
+ this.subtype = subType;
+ this.ethernetType = ethernetType;
+ }
+
+ public EthType ethernetType() {
+ return ethernetType;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return this.subtype;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("ethernetType", ethernetType())
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype, ethernetType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PushHeaderInstructions) {
+ PushHeaderInstructions that = (PushHeaderInstructions) obj;
+ return Objects.equals(subtype, that.subtype) &&
+ Objects.equals(this.ethernetType, that.ethernetType);
+ }
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Represents a VLAN id modification instruction.
+ */
+ public static final class ModVlanIdInstruction extends L2ModificationInstruction {
+
+ private final VlanId vlanId;
+
+ ModVlanIdInstruction(VlanId vlanId) {
+ this.vlanId = vlanId;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return L2SubType.VLAN_ID;
+ }
+
+ public VlanId vlanId() {
+ return this.vlanId;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("id", vlanId).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), vlanId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModVlanIdInstruction) {
+ ModVlanIdInstruction that = (ModVlanIdInstruction) obj;
+ return Objects.equals(vlanId, that.vlanId);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a VLAN PCP modification instruction.
+ */
+ public static final class ModVlanPcpInstruction extends L2ModificationInstruction {
+
+ private static final byte MASK = 0x7;
+ private final byte vlanPcp;
+
+ ModVlanPcpInstruction(byte vlanPcp) {
+ this.vlanPcp = (byte) (vlanPcp & MASK);
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return L2SubType.VLAN_PCP;
+ }
+
+ public byte vlanPcp() {
+ return this.vlanPcp;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("pcp", Long.toHexString(vlanPcp)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), vlanPcp);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModVlanPcpInstruction) {
+ ModVlanPcpInstruction that = (ModVlanPcpInstruction) obj;
+ return Objects.equals(vlanPcp, that.vlanPcp);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a VLAN POP modification instruction.
+ */
+ public static final class PopVlanInstruction extends L2ModificationInstruction {
+ private final L2SubType subtype;
+
+ PopVlanInstruction(L2SubType subType) {
+ this.subtype = subType;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return subtype;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PopVlanInstruction) {
+ PopVlanInstruction that = (PopVlanInstruction) obj;
+ return Objects.equals(subtype, that.subtype);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a MPLS label modification.
+ */
+ public static final class ModMplsLabelInstruction
+ extends L2ModificationInstruction {
+
+ private final MplsLabel mplsLabel;
+
+ ModMplsLabelInstruction(MplsLabel mplsLabel) {
+ this.mplsLabel = mplsLabel;
+ }
+
+ /**
+ * @deprecated in Drake Release.
+ * @return integer value of label
+ */
+ // Consider changing return value to MplsLabel
+ // after deprecation process so that it'll be symmetric to
+ // MplsCriterion#label()
+ @Deprecated
+ public Integer label() {
+ return mplsLabel.toInt();
+ }
+
+ public MplsLabel mplsLabel() {
+ return mplsLabel;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return L2SubType.MPLS_LABEL;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("mpls", mplsLabel).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), mplsLabel);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModMplsLabelInstruction) {
+ ModMplsLabelInstruction that = (ModMplsLabelInstruction) obj;
+ return Objects.equals(mplsLabel, that.mplsLabel);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a MPLS BOS modification.
+ */
+ public static final class ModMplsBosInstruction
+ extends L2ModificationInstruction {
+
+ private final boolean mplsBos;
+
+ ModMplsBosInstruction(boolean mplsBos) {
+ this.mplsBos = mplsBos;
+ }
+
+ public boolean mplsBos() {
+ return mplsBos;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return L2SubType.MPLS_BOS;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString()).add("bos", mplsBos)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), mplsBos);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModMplsBosInstruction) {
+ ModMplsBosInstruction that = (ModMplsBosInstruction) obj;
+ return Objects.equals(mplsBos, that.mplsBos());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a MPLS TTL modification.
+ */
+ public static final class ModMplsTtlInstruction
+ extends L2ModificationInstruction {
+
+ ModMplsTtlInstruction() {
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return L2SubType.DEC_MPLS_TTL;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModMplsTtlInstruction) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a Tunnel id modification.
+ */
+ public static final class ModTunnelIdInstruction
+ extends L2ModificationInstruction {
+
+ private final long tunnelId;
+
+ ModTunnelIdInstruction(long tunnelId) {
+ this.tunnelId = tunnelId;
+ }
+
+ public long tunnelId() {
+ return this.tunnelId;
+ }
+
+ @Override
+ public L2SubType subtype() {
+ return L2SubType.TUNNEL_ID;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("id", Long.toHexString(tunnelId))
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), tunnelId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModTunnelIdInstruction) {
+ ModTunnelIdInstruction that = (ModTunnelIdInstruction) obj;
+ return Objects.equals(tunnelId, that.tunnelId);
+ }
+ return false;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L3ModificationInstruction.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L3ModificationInstruction.java
new file mode 100644
index 00000000..41819504
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L3ModificationInstruction.java
@@ -0,0 +1,231 @@
+/*
+ * 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.instructions;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Objects;
+
+import org.onlab.packet.IpAddress;
+
+/**
+ * Abstraction of a single traffic treatment step.
+ */
+public abstract class L3ModificationInstruction implements Instruction {
+
+ /**
+ * Represents the type of traffic treatment.
+ */
+ public enum L3SubType {
+ /**
+ * IPv4 src modification.
+ */
+ IPV4_SRC,
+
+ /**
+ * IPv4 dst modification.
+ */
+ IPV4_DST,
+
+ /**
+ * IPv6 src modification.
+ */
+ IPV6_SRC,
+
+ /**
+ * IPv6 dst modification.
+ */
+ IPV6_DST,
+
+ /**
+ * IPv6 flow label modification.
+ */
+ IPV6_FLABEL,
+
+ /**
+ * Decrement TTL.
+ */
+ DEC_TTL,
+
+ /**
+ * Copy TTL out.
+ */
+ TTL_OUT,
+
+ /**
+ * Copy TTL in.
+ */
+ TTL_IN
+
+ //TODO: remaining types
+ }
+
+ /**
+ * Returns the subtype of the modification instruction.
+ * @return type of instruction
+ */
+ public abstract L3SubType subtype();
+
+ @Override
+ public final Type type() {
+ return Type.L3MODIFICATION;
+ }
+
+ /**
+ * Represents a L3 src/dst modification instruction.
+ */
+ public static final class ModIPInstruction extends L3ModificationInstruction {
+
+ private final L3SubType subtype;
+ private final IpAddress ip;
+
+ ModIPInstruction(L3SubType subType, IpAddress addr) {
+
+ this.subtype = subType;
+ this.ip = addr;
+ }
+
+ @Override
+ public L3SubType subtype() {
+ return this.subtype;
+ }
+
+ public IpAddress ip() {
+ return this.ip;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("ip", ip).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), ip);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModIPInstruction) {
+ ModIPInstruction that = (ModIPInstruction) obj;
+ return Objects.equals(ip, that.ip) &&
+ Objects.equals(this.subtype(), that.subtype());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a L3 IPv6 Flow Label (RFC 6437) modification instruction
+ * (20 bits unsigned integer).
+ */
+ public static final class ModIPv6FlowLabelInstruction
+ extends L3ModificationInstruction {
+ private static final int MASK = 0xfffff;
+ private final int flowLabel; // IPv6 flow label: 20 bits
+
+ /**
+ * Creates a new flow mod instruction.
+ *
+ * @param flowLabel the IPv6 flow label to set in the treatment (20 bits)
+ */
+ ModIPv6FlowLabelInstruction(int flowLabel) {
+ this.flowLabel = flowLabel & MASK;
+ }
+
+ @Override
+ public L3SubType subtype() {
+ return L3SubType.IPV6_FLABEL;
+ }
+
+ /**
+ * Gets the IPv6 flow label to set in the treatment.
+ *
+ * @return the IPv6 flow label to set in the treatment (20 bits)
+ */
+ public int flowLabel() {
+ return this.flowLabel;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("flowLabel", Long.toHexString(flowLabel)).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), flowLabel);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModIPv6FlowLabelInstruction) {
+ ModIPv6FlowLabelInstruction that =
+ (ModIPv6FlowLabelInstruction) obj;
+ return Objects.equals(flowLabel, that.flowLabel);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Represents a L3 TTL modification instruction.
+ */
+ public static final class ModTtlInstruction extends L3ModificationInstruction {
+
+ private final L3SubType subtype;
+
+ ModTtlInstruction(L3SubType subtype) {
+ this.subtype = subtype;
+ }
+
+ @Override
+ public L3SubType subtype() {
+ return this.subtype;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModTtlInstruction) {
+ ModTtlInstruction that = (ModTtlInstruction) obj;
+ return Objects.equals(this.subtype(), that.subtype());
+ }
+ return false;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L4ModificationInstruction.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L4ModificationInstruction.java
new file mode 100644
index 00000000..441a2c5f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/L4ModificationInstruction.java
@@ -0,0 +1,114 @@
+/*
+ * 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.instructions;
+
+import org.onlab.packet.TpPort;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Abstraction of a single traffic treatment step.
+ */
+public abstract class L4ModificationInstruction implements Instruction {
+
+ /**
+ * Represents the type of traffic treatment.
+ */
+ public enum L4SubType {
+ /**
+ * TCP src modification.
+ */
+ TCP_SRC,
+
+ /**
+ * TCP dst modification.
+ */
+ TCP_DST,
+
+ /**
+ * UDP src modification.
+ */
+ UDP_SRC,
+
+ /**
+ * UDP dst modification.
+ */
+ UDP_DST
+
+ //TODO: remaining types
+ }
+
+ /**
+ * Returns the subtype of the modification instruction.
+ *
+ * @return type of instruction
+ */
+ public abstract L4SubType subtype();
+
+ @Override
+ public Type type() {
+ return Type.L4MODIFICATION;
+ }
+
+ /**
+ * Represents a L4 src/dst modification instruction.
+ */
+ public static final class ModTransportPortInstruction extends L4ModificationInstruction {
+
+ private final L4SubType subtype;
+ private final TpPort port;
+
+ public ModTransportPortInstruction(L4SubType subtype, TpPort port) {
+ this.subtype = subtype;
+ this.port = port;
+ }
+
+ @Override
+ public L4SubType subtype() {
+ return this.subtype;
+ }
+
+ public TpPort port() {
+ return this.port;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(subtype().toString())
+ .add("port", port).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type(), subtype(), port);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ModTransportPortInstruction) {
+ ModTransportPortInstruction that = (ModTransportPortInstruction) obj;
+ return Objects.equals(port, that.port) &&
+ Objects.equals(this.subtype(), that.subtype());
+ }
+ return false;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/package-info.java
new file mode 100644
index 00000000..65f58a40
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/instructions/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.
+ */
+
+/**
+ * Traffic treatment model.
+ */
+package org.onosproject.net.flow.instructions;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/package-info.java
new file mode 100644
index 00000000..6068b6f2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flow/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+/**
+ * Flow rule model &amp; related services API definitions.
+ *
+ * <p>
+ * The figure below depicts the general interactions between different
+ * components of the intent subsystem.<br>
+ * <img src="doc-files/flow-design.png" alt="ONOS flow rule subsystem design">
+ * </p>
+ */
+package org.onosproject.net.flow;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java
new file mode 100644
index 00000000..7b5924fb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java
@@ -0,0 +1,239 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of a filtering objective.
+ */
+@Beta
+public final class DefaultFilteringObjective implements FilteringObjective {
+
+
+ private final Type type;
+ private final boolean permanent;
+ private final int timeout;
+ private final ApplicationId appId;
+ private final int priority;
+ private final Criterion key;
+ private final List<Criterion> conditions;
+ private final int id;
+ private final Operation op;
+ private final Optional<ObjectiveContext> context;
+
+ private DefaultFilteringObjective(Builder builder) {
+ this.key = builder.key;
+ this.type = builder.type;
+ this.permanent = builder.permanent;
+ this.timeout = builder.timeout;
+ this.appId = builder.appId;
+ this.priority = builder.priority;
+ this.conditions = builder.conditions;
+ this.op = builder.op;
+ this.context = Optional.ofNullable(builder.context);
+
+ this.id = Objects.hash(type, key, conditions, permanent,
+ timeout, appId, priority);
+ }
+
+ @Override
+ public Criterion key() {
+ return key;
+ }
+
+ @Override
+ public Type type() {
+ return this.type;
+ }
+
+ @Override
+ public Collection<Criterion> conditions() {
+ return conditions;
+ }
+
+ @Override
+ public int id() {
+ return id;
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
+ public int timeout() {
+ return timeout;
+ }
+
+ @Override
+ public boolean permanent() {
+ return permanent;
+ }
+
+ @Override
+ public Operation op() {
+ return op;
+ }
+
+ @Override
+ public Optional<ObjectiveContext> context() {
+ return context;
+ }
+
+ /**
+ * Returns a new builder.
+ *
+ * @return new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+
+ public static final class Builder implements FilteringObjective.Builder {
+ private final ImmutableList.Builder<Criterion> listBuilder
+ = ImmutableList.builder();
+
+ private Type type;
+ private boolean permanent = DEFAULT_PERMANENT;
+ private int timeout = DEFAULT_TIMEOUT;
+ private ApplicationId appId;
+ private int priority = DEFAULT_PRIORITY;
+ private Criterion key = Criteria.dummy();
+ private List<Criterion> conditions;
+ private Operation op;
+ private ObjectiveContext context;
+
+ @Override
+ public Builder withKey(Criterion key) {
+ this.key = key;
+ return this;
+ }
+
+ @Override
+ public Builder addCondition(Criterion criterion) {
+ listBuilder.add(criterion);
+ return this;
+ }
+
+ @Override
+ public Builder permit() {
+ this.type = Type.PERMIT;
+ return this;
+ }
+
+ @Override
+ public Builder deny() {
+ this.type = Type.DENY;
+ return this;
+ }
+
+ @Override
+ public Builder makeTemporary(int timeout) {
+ this.timeout = timeout;
+ permanent = false;
+ return this;
+ }
+
+ @Override
+ public Builder makePermanent() {
+ permanent = true;
+ return this;
+ }
+
+ @Override
+ public Builder fromApp(ApplicationId appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ @Override
+ public Builder withPriority(int priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ @Override
+ public FilteringObjective add() {
+ conditions = listBuilder.build();
+ op = Operation.ADD;
+ checkNotNull(type, "Must have a type.");
+ checkArgument(!conditions.isEmpty(), "Must have at least one condition.");
+ checkNotNull(appId, "Must supply an application id");
+
+ return new DefaultFilteringObjective(this);
+
+ }
+
+ @Override
+ public FilteringObjective remove() {
+ conditions = listBuilder.build();
+ checkNotNull(type, "Must have a type.");
+ checkArgument(!conditions.isEmpty(), "Must have at least one condition.");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.REMOVE;
+
+ return new DefaultFilteringObjective(this);
+
+ }
+
+ @Override
+ public FilteringObjective add(ObjectiveContext context) {
+ conditions = listBuilder.build();
+ checkNotNull(type, "Must have a type.");
+ checkArgument(!conditions.isEmpty(), "Must have at least one condition.");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.ADD;
+ this.context = context;
+
+ return new DefaultFilteringObjective(this);
+ }
+
+ @Override
+ public FilteringObjective remove(ObjectiveContext context) {
+ conditions = listBuilder.build();
+ checkNotNull(type, "Must have a type.");
+ checkArgument(!conditions.isEmpty(), "Must have at least one condition.");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.REMOVE;
+ this.context = context;
+
+ return new DefaultFilteringObjective(this);
+ }
+
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultForwardingObjective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultForwardingObjective.java
new file mode 100644
index 00000000..0abf5abe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultForwardingObjective.java
@@ -0,0 +1,241 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of a forwarding objective.
+ */
+@Beta
+public final class DefaultForwardingObjective implements ForwardingObjective {
+
+ private final TrafficSelector selector;
+ private final Flag flag;
+ private final boolean permanent;
+ private final int timeout;
+ private final ApplicationId appId;
+ private final int priority;
+ private final Integer nextId;
+ private final TrafficTreatment treatment;
+ private final Operation op;
+ private final Optional<ObjectiveContext> context;
+
+ private final int id;
+
+ private DefaultForwardingObjective(Builder builder) {
+ this.selector = builder.selector;
+ this.flag = builder.flag;
+ this.permanent = builder.permanent;
+ this.timeout = builder.timeout;
+ this.appId = builder.appId;
+ this.priority = builder.priority;
+ this.nextId = builder.nextId;
+ this.treatment = builder.treatment;
+ this.op = builder.op;
+ this.context = Optional.ofNullable(builder.context);
+
+ this.id = Objects.hash(selector, flag, permanent,
+ timeout, appId, priority, nextId,
+ treatment, op);
+ }
+
+
+ @Override
+ public TrafficSelector selector() {
+ return selector;
+ }
+
+ @Override
+ public Integer nextId() {
+ return nextId;
+ }
+
+ @Override
+ public TrafficTreatment treatment() {
+ return treatment;
+ }
+
+
+ @Override
+ public Flag flag() {
+ return flag;
+ }
+
+ @Override
+ public int id() {
+ return id;
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
+ public int timeout() {
+ return timeout;
+ }
+
+ @Override
+ public boolean permanent() {
+ return permanent;
+ }
+
+ @Override
+ public Operation op() {
+ return op;
+ }
+
+ @Override
+ public Optional<ObjectiveContext> context() {
+ return context;
+ }
+
+ /**
+ * Returns a new builder.
+ *
+ * @return new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder implements ForwardingObjective.Builder {
+
+ private TrafficSelector selector;
+ private Flag flag;
+ private boolean permanent = DEFAULT_PERMANENT;
+ private int timeout = DEFAULT_TIMEOUT;
+ private int priority = DEFAULT_PRIORITY;
+ private ApplicationId appId;
+ private Integer nextId;
+ private TrafficTreatment treatment;
+ private Operation op;
+ private ObjectiveContext context;
+
+ @Override
+ public Builder withSelector(TrafficSelector selector) {
+ this.selector = selector;
+ return this;
+ }
+
+ @Override
+ public Builder nextStep(int nextId) {
+ this.nextId = nextId;
+ return this;
+ }
+
+ @Override
+ public Builder withTreatment(TrafficTreatment treatment) {
+ this.treatment = treatment;
+ return this;
+ }
+
+ @Override
+ public Builder withFlag(Flag flag) {
+ this.flag = flag;
+ return this;
+ }
+
+ @Override
+ public Builder makeTemporary(int timeout) {
+ this.timeout = timeout;
+ this.permanent = false;
+ return this;
+ }
+
+ @Override
+ public Builder makePermanent() {
+ this.permanent = true;
+ return this;
+ }
+
+ @Override
+ public Builder fromApp(ApplicationId appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ @Override
+ public Builder withPriority(int priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ @Override
+ public ForwardingObjective add() {
+ checkNotNull(selector, "Must have a selector");
+ checkNotNull(flag, "A flag must be set");
+ checkArgument(nextId != null || treatment != null, "Must supply at " +
+ "least a treatment and/or a nextId");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.ADD;
+ return new DefaultForwardingObjective(this);
+ }
+
+ @Override
+ public ForwardingObjective remove() {
+ checkNotNull(selector, "Must have a selector");
+ checkNotNull(flag, "A flag must be set");
+ checkArgument(nextId != null || treatment != null, "Must supply at " +
+ "least a treatment and/or a nextId");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.REMOVE;
+ return new DefaultForwardingObjective(this);
+ }
+
+ @Override
+ public ForwardingObjective add(ObjectiveContext context) {
+ checkNotNull(selector, "Must have a selector");
+ checkNotNull(flag, "A flag must be set");
+ checkArgument(nextId != null || treatment != null, "Must supply at " +
+ "least a treatment and/or a nextId");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.ADD;
+ this.context = context;
+
+ return new DefaultForwardingObjective(this);
+ }
+
+ @Override
+ public ForwardingObjective remove(ObjectiveContext context) {
+ checkNotNull(selector, "Must have a selector");
+ checkNotNull(flag, "A flag must be set");
+ checkArgument(nextId != null || treatment != null, "Must supply at " +
+ "least a treatment and/or a nextId");
+ checkNotNull(appId, "Must supply an application id");
+ op = Operation.REMOVE;
+ this.context = context;
+
+ return new DefaultForwardingObjective(this);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java
new file mode 100644
index 00000000..20e89295
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java
@@ -0,0 +1,222 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of a next objective.
+ */
+@Beta
+public final class DefaultNextObjective implements NextObjective {
+
+ private final List<TrafficTreatment> treatments;
+ private final ApplicationId appId;
+ private final Type type;
+ private final Integer id;
+ private final Operation op;
+ private final Optional<ObjectiveContext> context;
+
+ private DefaultNextObjective(Builder builder) {
+ this.treatments = builder.treatments;
+ this.appId = builder.appId;
+ this.type = builder.type;
+ this.id = builder.id;
+ this.op = builder.op;
+ this.context = Optional.ofNullable(builder.context);
+ }
+
+ @Override
+ public Collection<TrafficTreatment> next() {
+ return treatments;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public int id() {
+ return id;
+ }
+
+ @Override
+ public int priority() {
+ return 0;
+ }
+
+ @Override
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
+ public int timeout() {
+ return 0;
+ }
+
+ @Override
+ public boolean permanent() {
+ return false;
+ }
+
+ @Override
+ public Operation op() {
+ return op;
+ }
+
+ @Override
+ public Optional<ObjectiveContext> context() {
+ return context;
+ }
+
+ /**
+ * Returns a new builder.
+ *
+ * @return new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder implements NextObjective.Builder {
+
+ private ApplicationId appId;
+ private Type type;
+ private Integer id;
+ private List<TrafficTreatment> treatments;
+ private Operation op;
+ private ObjectiveContext context;
+
+ private final ImmutableList.Builder<TrafficTreatment> listBuilder
+ = ImmutableList.builder();
+
+ @Override
+ public Builder withId(int nextId) {
+ this.id = nextId;
+ return this;
+ }
+
+ @Override
+ public Builder withType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public Builder addTreatment(TrafficTreatment treatment) {
+ listBuilder.add(treatment);
+ return this;
+ }
+
+ /**
+ * Noop. This method has no effect.
+ *
+ * @param timeout a timeout
+ * @return a next objective builder
+ */
+ @Override
+ public Builder makeTemporary(int timeout) {
+ return this;
+ }
+
+ /**
+ * Noop. This method has no effect.
+ *
+ * @return a next objective builder
+ */
+ @Override
+ public Builder makePermanent() {
+ return this;
+ }
+
+ @Override
+ public Builder fromApp(ApplicationId appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ /**
+ * Noop. This method has no effect.
+ *
+ * @param priority an integer
+ * @return a next objective builder
+ */
+ @Override
+ public Builder withPriority(int priority) {
+ return this;
+ }
+
+ @Override
+ public NextObjective add() {
+ treatments = listBuilder.build();
+ op = Operation.ADD;
+ checkNotNull(appId, "Must supply an application id");
+ checkNotNull(id, "id cannot be null");
+ checkNotNull(type, "The type cannot be null");
+ checkArgument(!treatments.isEmpty(), "Must have at least one treatment");
+
+ return new DefaultNextObjective(this);
+ }
+
+ @Override
+ public NextObjective remove() {
+ treatments = listBuilder.build();
+ op = Operation.REMOVE;
+ checkNotNull(appId, "Must supply an application id");
+ checkNotNull(id, "id cannot be null");
+ checkNotNull(type, "The type cannot be null");
+
+ return new DefaultNextObjective(this);
+ }
+
+ @Override
+ public NextObjective add(ObjectiveContext context) {
+ treatments = listBuilder.build();
+ op = Operation.ADD;
+ this.context = context;
+ checkNotNull(appId, "Must supply an application id");
+ checkNotNull(id, "id cannot be null");
+ checkNotNull(type, "The type cannot be null");
+ checkArgument(!treatments.isEmpty(), "Must have at least one treatment");
+
+ return new DefaultNextObjective(this);
+ }
+
+ @Override
+ public NextObjective remove(ObjectiveContext context) {
+ treatments = listBuilder.build();
+ op = Operation.REMOVE;
+ this.context = context;
+ checkNotNull(appId, "Must supply an application id");
+ checkNotNull(id, "id cannot be null");
+ checkNotNull(type, "The type cannot be null");
+
+ return new DefaultNextObjective(this);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java
new file mode 100644
index 00000000..58304571
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java
@@ -0,0 +1,158 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import java.util.Collection;
+
+/**
+ * Represents a filtering flow objective. Each filtering flow objective
+ * is made up of a key (criterion) to a set of criteria. Using this information
+ * a pipeline aware driver will decide how this objective should be mapped
+ * to the specific device pipeline. For example, consider the following
+ * filtering objective:
+ *
+ * portX -&gt; {MAC1, IP1, MAC2}
+ *
+ * The driver could decide to pass L3 packet to the L3 table and L2 packets to
+ * the L2 table for packets arriving on portX.
+ *
+ * Filtering objectives do not only represent what should be permitted into the
+ * pipeline but can also be used to deny or drop unwanted packets by specifying
+ * the appropriate type of filtering objective. It is also important to note
+ * that submitting a filtering objective does not necessarily result in rules
+ * programmed at the switch, the driver is free to decide when these rules are
+ * programmed. For example, a filtering rule may only be programmed once a
+ * corresponding forwarding objective has been received.
+ */
+@Beta
+public interface FilteringObjective extends Objective {
+
+ enum Type {
+ /**
+ * Enables the filtering condition.
+ */
+ PERMIT,
+
+ /**
+ * Disables the filtering condition.
+ */
+ DENY
+ }
+
+ /**
+ * Obtain the key for this filter.
+ *
+ * @return a criterion
+ */
+ Criterion key();
+
+ /**
+ * Obtain this filtering type.
+ *
+ * @return the type
+ */
+ Type type();
+
+ /**
+ * The set of conditions the filter must provision at the device.
+ *
+ * @return a collection of criteria
+ */
+ Collection<Criterion> conditions();
+
+ /**
+ * Builder of Filtering objective entities.
+ */
+ interface Builder extends Objective.Builder {
+
+ /**
+ * Specify the key for the filter.
+ *
+ * @param key a criterion
+ * @return a filter objective builder
+ */
+ Builder withKey(Criterion key);
+
+ /**
+ * Add a filtering condition.
+ *
+ * @param criterion new criterion
+ * @return a filtering builder
+ */
+ Builder addCondition(Criterion criterion);
+
+ /**
+ * Permit this filtering condition set.
+ *
+ * @return a filtering builder
+ */
+ Builder permit();
+
+ /**
+ * Deny this filtering condition set.
+ *
+ * @return a filtering builder
+ */
+ Builder deny();
+
+ /**
+ * Assigns an application id.
+ *
+ * @param appId an application id
+ * @return a filtering builder
+ */
+ Builder fromApp(ApplicationId appId);
+
+ /**
+ * Builds the filtering objective that will be added.
+ *
+ * @return a filtering objective
+ */
+ FilteringObjective add();
+
+ /**
+ * Builds the filtering objective that will be removed.
+ *
+ * @return a filtering objective.
+ */
+ FilteringObjective remove();
+
+ /**
+ * Builds the filtering objective that will be added.
+ * The context will be used to notify the calling application.
+ *
+ * @param context an objective context
+ * @return a filtering objective
+ */
+ FilteringObjective add(ObjectiveContext context);
+
+ /**
+ * Builds the filtering objective that will be removed.
+ * The context will be used to notify the calling application.
+ *
+ * @param context an objective context
+ * @return a filtering objective
+ */
+ FilteringObjective remove(ObjectiveContext context);
+
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveService.java
new file mode 100644
index 00000000..d3254151
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveService.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Service for programming data plane flow rules in manner independent of
+ * specific device table pipeline configuration.
+ */
+@Beta
+public interface FlowObjectiveService {
+
+ /**
+ * Installs the filtering rules onto the specified device.
+ *
+ * @param deviceId device identifier
+ * @param filteringObjective the filtering objective
+ */
+ void filter(DeviceId deviceId, FilteringObjective filteringObjective);
+
+ /**
+ * Installs the forwarding rules onto the specified device.
+ *
+ * @param deviceId device identifier
+ * @param forwardingObjective the forwarding objective
+ */
+ void forward(DeviceId deviceId, ForwardingObjective forwardingObjective);
+
+ /**
+ * Installs the next hop elements into the specified device.
+ *
+ * @param deviceId device identifier
+ * @param nextObjective a next objective
+ */
+ void next(DeviceId deviceId, NextObjective nextObjective);
+
+ /**
+ * Obtains a globally unique next objective.
+ *
+ * @return an integer
+ */
+ int allocateNextId();
+
+ /**
+ * Installs the filtering rules onto the specified device.
+ *
+ * @param policy policy expression
+ */
+ void initPolicy(String policy);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStore.java
new file mode 100644
index 00000000..ecf5d733
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStore.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.store.Store;
+
+/**
+ * The flow objective store.
+ */
+@Beta
+public interface FlowObjectiveStore
+ extends Store<ObjectiveEvent, FlowObjectiveStoreDelegate> {
+
+ /**
+ * Adds a NextGroup to the store.
+ *
+ * @param nextId an integer
+ * @param group a next group opaque object
+ */
+ void putNextGroup(Integer nextId, NextGroup group);
+
+ /**
+ * Fetch a next group from the store.
+ * @param nextId an integer
+ * @return a next group
+ */
+ NextGroup getNextGroup(Integer nextId);
+
+ /**
+ * Allocates a next objective id. This id is globally unique
+ *
+ * @return an integer
+ */
+ int allocateNextId();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStoreDelegate.java
new file mode 100644
index 00000000..2189af1b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/FlowObjectiveStoreDelegate.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Flow Objective store delegate abstraction.
+ */
+@Beta
+public interface FlowObjectiveStoreDelegate extends StoreDelegate<ObjectiveEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java
new file mode 100644
index 00000000..9857a710
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java
@@ -0,0 +1,158 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+/**
+ * Represents a description of which types of traffic need to
+ * be forwarded through the device. A forwarding objective may
+ * result in multiple rules at the device. There are two main types
+ * of forwarding objectives:
+ *
+ * - Versatile
+ * - Specific
+ *
+ * A versatile forwarding objective represents a composite rule that matches
+ * two or more header fields. The use of versatile usually indicates that this
+ * rule should be inserted in its entirety into the ACL table. Although,
+ * drivers for some devices are free to implement this differently.
+ *
+ * A specific forwarding objective represents a specific rule matching one or
+ * more header fields. The installation of this rule may result in several rules
+ * at the device. For example, one per table type.
+ */
+@Beta
+public interface ForwardingObjective extends Objective {
+
+ /**
+ * Represents whether this objective is monolithic or
+ * may be broken down into parts.
+ */
+ enum Flag {
+ /**
+ * A decomposable objective.
+ */
+ SPECIFIC,
+
+ /**
+ * A monolithic objective.
+ */
+ VERSATILE
+ }
+
+ /**
+ * Obtain the selector for this objective.
+ *
+ * @return a traffic selector
+ */
+ TrafficSelector selector();
+
+ /**
+ * Obtain the traffic treatment for this objective. Mutually exclusive with
+ * 'treatment'.
+ *
+ * @return an integer
+ */
+ Integer nextId();
+
+ /**
+ * A traffic treatment for this forwarding objective. Mutually exclusive
+ * with a nextId.
+ *
+ * @return a traffic treatment
+ */
+ TrafficTreatment treatment();
+
+ /**
+ * Obtain the type of this objective.
+ *
+ * @return a flag type
+ */
+ Flag flag();
+
+ /**
+ * A forwarding objective builder.
+ */
+ interface Builder extends Objective.Builder {
+
+ /**
+ * Assigns a selector to the forwarding objective.
+ *
+ * @param selector a traffic selector
+ * @return a forwarding objective builder
+ */
+ Builder withSelector(TrafficSelector selector);
+
+ /**
+ * Assigns a next step to the forwarding objective.
+ *
+ * @param nextId a next objective id.
+ * @return a forwarding objective builder
+ */
+ Builder nextStep(int nextId);
+
+ /**
+ * Assigns the treatment for this forwarding objective.
+ *
+ * @param treatment a traffic treatment
+ * @return a forwarding objective
+ */
+ Builder withTreatment(TrafficTreatment treatment);
+
+ /**
+ * Assigns the flag to the forwarding objective.
+ *
+ * @param flag a flag
+ * @return a forwarding objective builder
+ */
+ Builder withFlag(Flag flag);
+
+ /**
+ * Builds the forwarding objective that will be added.
+ *
+ * @return a forwarding objective
+ */
+ ForwardingObjective add();
+
+ /**
+ * Builds the forwarding objective that will be removed.
+ *
+ * @return a forwarding objective.
+ */
+ ForwardingObjective remove();
+
+ /**
+ * Builds the forwarding objective that will be added.
+ * The context will be used to notify the calling application.
+ *
+ * @param context an objective context
+ * @return a forwarding objective
+ */
+ ForwardingObjective add(ObjectiveContext context);
+
+ /**
+ * Builds the forwarding objective that will be removed.
+ * The context will be used to notify the calling application.
+ *
+ * @param context an objective context
+ * @return a forwarding objective
+ */
+ ForwardingObjective remove(ObjectiveContext context);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java
new file mode 100644
index 00000000..1350d7a1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Collection;
+
+/**
+ * Represents a nexthop which will be translated by a driver
+ * into the appropriate group or actions needed to implement
+ * the egress function.
+ *
+ * A next objective is made up of a collection of traffic treatments
+ * associated with a type. These types are:
+ *
+ * - Hashed
+ * - Broadcast
+ * - Failover
+ * - Simple
+ *
+ * These types will indicate to the driver what the intended behaviour is.
+ * For example, a broadcast next objective with a collection of output
+ * treatments will indicate to a driver that all output actions are expected
+ * to be executed simultaneously. The driver is then free to implement this
+ * as a group or a simple action list.
+ */
+@Beta
+public interface NextObjective extends Objective {
+
+ /**
+ * Represents the type of next phase to build.
+ */
+ enum Type {
+ /**
+ * A hashed packet processing.
+ */
+ HASHED,
+
+ /**
+ * Broadcast packet process.
+ */
+ BROADCAST,
+
+ /**
+ * Failover handling.
+ */
+ FAILOVER,
+
+ /**
+ * Simple processing. Could be a group or a treatment.
+ */
+ SIMPLE
+ }
+
+ /**
+ * The collection of treatments that need to be applied to a set of traffic.
+ *
+ * @return a collection of traffic treatments
+ */
+ Collection<TrafficTreatment> next();
+
+ /**
+ * The type of operation that will be applied to the traffic using the collection
+ * of treatments.
+ *
+ * @return a type
+ */
+ Type type();
+
+ /**
+ * A next step builder.
+ */
+ interface Builder extends Objective.Builder {
+
+ /**
+ * Specifies the id for this next objective.
+ *
+ * @param nextId an integer
+ * @return a next objective builder
+ */
+ Builder withId(int nextId);
+
+ /**
+ * Sets the type of next step.
+ *
+ * @param type a type
+ * @return a next step builder
+ */
+ Builder withType(Type type);
+
+ /**
+ * Adds a treatment to this next step.
+ *
+ * @param treatment a traffic treatment
+ * @return a next step builder
+ */
+ Builder addTreatment(TrafficTreatment treatment);
+
+ /**
+ * Specifies the application which applied the filter.
+ *
+ * @param appId an application id
+ * @return an objective builder
+ */
+ @Override
+ Builder fromApp(ApplicationId appId);
+
+ /**
+ * Sets the priority for this objective.
+ *
+ * @param priority an integer
+ * @return an objective builder
+ */
+ @Override
+ Builder withPriority(int priority);
+
+ /**
+ * Builds the next objective that will be added.
+ *
+ * @return a next objective
+ */
+ NextObjective add();
+
+ /**
+ * Builds the next objective that will be removed.
+ *
+ * @return a next objective.
+ */
+ NextObjective remove();
+
+ /**
+ * Builds the next objective that will be added.
+ * The context will be used to notify the calling application.
+ *
+ * @param context an objective context
+ * @return a next objective
+ */
+ NextObjective add(ObjectiveContext context);
+
+ /**
+ * Builds the next objective that will be removed.
+ * The context will be used to notify the calling application.
+ *
+ * @param context an objective context
+ * @return a next objective
+ */
+ NextObjective remove(ObjectiveContext context);
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java
new file mode 100644
index 00000000..090c298c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/Objective.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+
+import java.util.Optional;
+
+/**
+ * Base representation of an flow description.
+ */
+@Beta
+public interface Objective {
+
+ static final boolean DEFAULT_PERMANENT = true;
+ static final int DEFAULT_TIMEOUT = 0;
+ static final int DEFAULT_PRIORITY = 32768;
+
+ /**
+ * Type of operation.
+ */
+ enum Operation {
+ /**
+ * Adds the objective.
+ */
+ ADD,
+
+ /**
+ * Removes the objective.
+ */
+ REMOVE
+ }
+
+ /**
+ * An identifier for this objective.
+ *
+ * @return an integer
+ */
+ int id();
+
+ /**
+ * The priority for this objective.
+ *
+ * @return an integer
+ */
+ int priority();
+
+ /**
+ * The application which applied this objective.
+ *
+ * @return an application id
+ */
+ ApplicationId appId();
+
+ /**
+ * The timeout for this objective.
+ *
+ * @return an integer
+ */
+ int timeout();
+
+ /**
+ * Whether this objective is permanent.
+ *
+ * @return a boolean
+ */
+ boolean permanent();
+
+ /**
+ * The type of operation for this objective.
+ *
+ * @return an operation
+ */
+ Operation op();
+
+ /**
+ * Obtains an optional context.
+ *
+ * @return optional; which will be empty if there is no context.
+ * Otherwise it will return the context.
+ */
+ Optional<ObjectiveContext> context();
+
+ /**
+ * An objective builder.
+ */
+ interface Builder {
+ /**
+ * Makes the filtering objective temporary.
+ *
+ * @param timeout a timeout
+ * @return an objective builder
+ */
+ Builder makeTemporary(int timeout);
+
+ /**
+ * Makes the filtering objective permanent.
+ *
+ * @return an objective builder
+ */
+ Builder makePermanent();
+
+ /**
+ * Specifies the application which applied the filter.
+ *
+ * @param appId an application id
+ * @return an objective builder
+ */
+ Builder fromApp(ApplicationId appId);
+
+ /**
+ * Sets the priority for this objective.
+ *
+ * @param priority an integer
+ * @return an objective builder
+ */
+ Builder withPriority(int priority);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveContext.java
new file mode 100644
index 00000000..f3d23e4a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveContext.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * The context of a objective that will become the subject of
+ * the notification.
+ * <p>
+ * Implementations of this class must be serializable.
+ * </p>
+ */
+@Beta
+public interface ObjectiveContext {
+
+ /**
+ * Invoked on successful execution of the flow objective.
+ *
+ * @param objective objective to execute
+ */
+ default void onSuccess(Objective objective) {
+ }
+
+ /**
+ * Invoked when error is encountered while executing the flow objective.
+ *
+ * @param objective objective to execute
+ * @param error error encountered
+ */
+ default void onError(Objective objective, ObjectiveError error) {
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveError.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveError.java
new file mode 100644
index 00000000..fd159d7e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveError.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Represents the set of errors possible when processing an objective.
+ */
+@Beta
+public enum ObjectiveError {
+
+ /**
+ * The driver processing this objective does not know how to process it.
+ */
+ UNSUPPORTED,
+
+ /**
+ * The flow installation for this objective failed.
+ */
+ FLOWINSTALLATIONFAILED,
+
+ /**
+ * THe group installation for this objective failed.
+ */
+ GROUPINSTALLATIONFAILED,
+
+ /**
+ * The group was reported as installed but is missing.
+ */
+ GROUPMISSING,
+
+ /**
+ * The device was not available to install objectives to.
+ */
+ DEVICEMISSING,
+
+ /**
+ * Incorrect Objective parameters passed in by the caller.
+ */
+ BADPARAMS,
+
+ /**
+ * An unknown error occurred.
+ */
+ UNKNOWN
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveEvent.java
new file mode 100644
index 00000000..c6937e31
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/ObjectiveEvent.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes a objective event.
+ */
+@Beta
+public class ObjectiveEvent extends AbstractEvent<ObjectiveEvent.Type, Integer> {
+
+ /**
+ * Type of objective events.
+ */
+ public enum Type {
+ /**
+ * Signifies that the objective has been added to the store.
+ */
+ ADD,
+
+ /**
+ * Signifies that the objective has been removed.
+ */
+ REMOVE
+ }
+
+ /**
+ * Creates an event of the given type for the specified objective id.
+ *
+ * @param type the type of the event
+ * @param objective the objective id the event is about
+ */
+ public ObjectiveEvent(Type type, Integer objective) {
+ super(type, objective);
+ }
+
+ /**
+ * Creates an event of the given type for the specified objective id at the given
+ * time.
+ *
+ * @param type the type of the event
+ * @param objective the objective id the event is about
+ * @param time the time of the event
+ */
+ public ObjectiveEvent(Type type, Integer objective, long time) {
+ super(type, objective, time);
+ }
+}
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/package-info.java
new file mode 100644
index 00000000..105f7b59
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/flowobjective/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Abstractions for objective-based flow programming of data plane without
+ * requiring device pipeline structure awareness.&nbsp; This subsystem is
+ * experimental and its interfaces will change in the upcoming release.
+ */
+package org.onosproject.net.flowobjective; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java
new file mode 100644
index 00000000..546a4513
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroup.java
@@ -0,0 +1,229 @@
+/*
+ * 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;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Objects;
+
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * ONOS implementation of default group that is stored in the system.
+ */
+public class DefaultGroup extends DefaultGroupDescription
+ implements Group, StoredGroupEntry {
+
+ private GroupState state;
+ private boolean isGroupStateAddedFirstTime;
+ private long life;
+ private long packets;
+ private long bytes;
+ private long referenceCount;
+ private GroupId id;
+
+ /**
+ * Initializes default values.
+ *
+ * @param newId group id for new group
+ */
+ private void initialize(GroupId newId) {
+ id = newId;
+ state = GroupState.PENDING_ADD;
+ life = 0;
+ packets = 0;
+ bytes = 0;
+ referenceCount = 0;
+ }
+
+ /**
+ * Default group object constructor with the parameters.
+ *
+ * @param id group identifier
+ * @param groupDesc group description parameters
+ */
+ public DefaultGroup(GroupId id, GroupDescription groupDesc) {
+ super(groupDesc);
+ initialize(id);
+ }
+
+ /**
+ * Default group object constructor with the available information
+ * from data plane.
+ *
+ * @param id group identifier
+ * @param deviceId device identifier
+ * @param type type of the group
+ * @param buckets immutable list of group bucket
+ */
+ public DefaultGroup(GroupId id,
+ DeviceId deviceId,
+ GroupDescription.Type type,
+ GroupBuckets buckets) {
+ super(deviceId, type, buckets);
+ initialize(id);
+ }
+
+ /**
+ * Returns group identifier associated with a group object.
+ *
+ * @return GroupId Group Identifier
+ */
+ @Override
+ public GroupId id() {
+ return this.id;
+ }
+
+ /**
+ * Returns current state of a group object.
+ *
+ * @return GroupState Group State
+ */
+ @Override
+ public GroupState state() {
+ return this.state;
+ }
+
+ /**
+ * Returns the number of milliseconds this group has been alive.
+ *
+ * @return number of millis
+ */
+ @Override
+ public long life() {
+ return this.life;
+ }
+
+ /**
+ * Returns the number of packets processed by this group.
+ *
+ * @return number of packets
+ */
+ @Override
+ public long packets() {
+ return this.packets;
+ }
+
+ /**
+ * Returns the number of bytes processed by this group.
+ *
+ * @return number of bytes
+ */
+ @Override
+ public long bytes() {
+ return this.bytes;
+ }
+
+ /**
+ * Sets the new state for this entry.
+ *
+ * @param newState new group entry state.
+ */
+ @Override
+ public void setState(Group.GroupState newState) {
+ this.state = newState;
+ }
+
+ /**
+ * Sets how long this entry has been entered in the system.
+ *
+ * @param life epoch time
+ */
+ @Override
+ public void setLife(long life) {
+ this.life = life;
+ }
+
+ /**
+ * Sets number of packets processed by this group entry.
+ *
+ * @param packets a long value
+ */
+ @Override
+ public void setPackets(long packets) {
+ this.packets = packets;
+ }
+
+ /**
+ * Sets number of bytes processed by this group entry.
+ *
+ * @param bytes a long value
+ */
+ @Override
+ public void setBytes(long bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public void setReferenceCount(long referenceCount) {
+ this.referenceCount = referenceCount;
+ }
+
+ @Override
+ public long referenceCount() {
+ return referenceCount;
+ }
+
+ /*
+ * The deviceId, type and buckets are used for hash.
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(id);
+ }
+
+ /*
+ * The deviceId, groupId, type and buckets should be same.
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultGroup) {
+ DefaultGroup that = (DefaultGroup) obj;
+ return super.equals(obj) &&
+ Objects.equals(id, that.id);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("description", super.toString())
+ .add("groupid", id)
+ .add("state", state)
+ .toString();
+ }
+
+ @Override
+ public void setIsGroupStateAddedFirstTime(boolean isGroupStateAddedFirstTime) {
+ this.isGroupStateAddedFirstTime = isGroupStateAddedFirstTime;
+ }
+
+ @Override
+ public boolean isGroupStateAddedFirstTime() {
+ return isGroupStateAddedFirstTime;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
new file mode 100644
index 00000000..6efd3e79
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
@@ -0,0 +1,252 @@
+/*
+ * 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;
+
+import org.onosproject.core.GroupId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Group bucket implementation. A group bucket is collection of
+ * instructions that can be performed on a traffic flow. A select
+ * Group can have one or more Buckets where traffic will be
+ * processed by a single bucket in the group, based on device
+ * specific selection algorithm (e.g. hash on some fields of the
+ * incoming traffic flows or round robin) and hence can contains
+ * optional weight field to define the weights among the buckets
+ * in the group. A failover group bucket is associated with a
+ * specific port or group that controls its liveness.
+ */
+public final class DefaultGroupBucket implements GroupBucket, StoredGroupBucketEntry {
+ private final GroupDescription.Type type;
+ private final TrafficTreatment treatment;
+ private final short weight;
+ private final PortNumber watchPort;
+ private final GroupId watchGroup;
+ private long packets;
+ private long bytes;
+
+ /**
+ * Group bucket constructor with the parameters.
+ *
+ * @param type group bucket type
+ * @param treatment traffic treatment associated with group bucket
+ * @param weight optional weight associated with group bucket
+ * @param watchPort port that determines the liveness of group bucket
+ * @param watchGroup group that determines the liveness of group bucket
+ */
+ private DefaultGroupBucket(GroupDescription.Type type,
+ TrafficTreatment treatment,
+ short weight,
+ PortNumber watchPort,
+ GroupId watchGroup) {
+ this.type = type;
+ this.treatment = checkNotNull(treatment);
+ this.weight = weight;
+ this.watchPort = watchPort;
+ this.watchGroup = watchGroup;
+ }
+
+ /**
+ * Creates indirect group bucket.
+ *
+ * @param treatment traffic treatment associated with group bucket
+ * @return indirect group bucket object
+ */
+ public static GroupBucket createIndirectGroupBucket(
+ TrafficTreatment treatment) {
+ return new DefaultGroupBucket(GroupDescription.Type.INDIRECT,
+ treatment,
+ (short) -1,
+ null,
+ null);
+ }
+
+ /**
+ * Creates select group bucket with weight as 1.
+ *
+ * @param treatment traffic treatment associated with group bucket
+ * @return select group bucket object
+ */
+ public static GroupBucket createSelectGroupBucket(
+ TrafficTreatment treatment) {
+ return new DefaultGroupBucket(GroupDescription.Type.SELECT,
+ treatment,
+ (short) 1,
+ null,
+ null);
+ }
+
+ /**
+ * Creates select group bucket with specified weight.
+ *
+ * @param treatment traffic treatment associated with group bucket
+ * @param weight weight associated with group bucket
+ * @return select group bucket object
+ */
+ public static GroupBucket createSelectGroupBucket(
+ TrafficTreatment treatment,
+ short weight) {
+ if (weight == 0) {
+ return null;
+ }
+
+ return new DefaultGroupBucket(GroupDescription.Type.SELECT,
+ treatment,
+ weight,
+ null,
+ null);
+ }
+
+ /**
+ * Creates failover group bucket with watchport or watchgroup.
+ *
+ * @param treatment traffic treatment associated with group bucket
+ * @param watchPort port that determines the liveness of group bucket
+ * @param watchGroup group that determines the liveness of group bucket
+ * @return failover group bucket object
+ */
+ public static GroupBucket createFailoverGroupBucket(
+ TrafficTreatment treatment,
+ PortNumber watchPort,
+ GroupId watchGroup) {
+ checkArgument(((watchPort != null) || (watchGroup != null)));
+ return new DefaultGroupBucket(GroupDescription.Type.FAILOVER,
+ treatment,
+ (short) -1,
+ watchPort,
+ watchGroup);
+ }
+
+ @Override
+ public GroupDescription.Type type() {
+ return this.type;
+ }
+
+ /**
+ * Returns list of Traffic instructions that are part of the bucket.
+ *
+ * @return TrafficTreatment Traffic instruction list
+ */
+ @Override
+ public TrafficTreatment treatment() {
+ return treatment;
+ }
+
+ /**
+ * Returns weight of select group bucket.
+ *
+ * @return short weight associated with a bucket
+ */
+ @Override
+ public short weight() {
+ return weight;
+ }
+
+ /**
+ * Returns port number used for liveness detection for a
+ * failover bucket.
+ *
+ * @return PortNumber port number used for liveness detection
+ */
+ @Override
+ public PortNumber watchPort() {
+ return watchPort;
+ }
+
+ /**
+ * Returns group identifier used for liveness detection for a
+ * failover bucket.
+ *
+ * @return GroupId group identifier to be used for liveness detection
+ */
+ @Override
+ public GroupId watchGroup() {
+ return watchGroup;
+ }
+
+ /*
+ * The type and treatment can change on a given bucket
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, treatment);
+ }
+
+ /*
+ * The priority and statistics can change on a given treatment and selector
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultGroupBucket) {
+ DefaultGroupBucket that = (DefaultGroupBucket) obj;
+ List<Instruction> myInstructions = this.treatment.allInstructions();
+ List<Instruction> theirInstructions = that.treatment.allInstructions();
+
+ return Objects.equals(type, that.type) &&
+ myInstructions.containsAll(theirInstructions) &&
+ theirInstructions.containsAll(myInstructions);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("type", type)
+ .add("treatment", treatment)
+ .add("packets", packets)
+ .add("bytes", bytes)
+ .toString();
+ }
+
+ @Override
+ public long packets() {
+ return packets;
+ }
+
+ @Override
+ public long bytes() {
+ return bytes;
+ }
+
+ @Override
+ public void setPackets(long packets) {
+ this.packets = packets;
+ }
+
+ @Override
+ public void setBytes(long bytes) {
+ this.bytes = bytes;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java
new file mode 100644
index 00000000..1580d835
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupDescription.java
@@ -0,0 +1,204 @@
+/*
+ * 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;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Default implementation of group description interface.
+ */
+public class DefaultGroupDescription implements GroupDescription {
+ private final GroupDescription.Type type;
+ private final GroupBuckets buckets;
+ private final GroupKey appCookie;
+ private final ApplicationId appId;
+ private final DeviceId deviceId;
+ private final Integer givenGroupId;
+
+ /**
+ * Constructor to be used by north bound applications.
+ * NOTE: The caller of this subsystem MUST ensure the appCookie
+ * provided in this API is immutable.
+ * NOTE: The caller may choose to pass in 'null' for the groupId. This is
+ * the typical case, where the caller allows the group subsystem to choose
+ * the groupId in a globally unique way. If the caller passes in the groupId,
+ * the caller MUST ensure that the id is globally unique (not just unique
+ * per device).
+ *
+ * @param deviceId device identifier
+ * @param type type of the group
+ * @param buckets immutable list of group bucket
+ * @param appCookie immutable application cookie of type DefaultGroupKey
+ * to be associated with the group
+ * @param groupId group identifier
+ * @param appId application id
+ */
+ public DefaultGroupDescription(DeviceId deviceId,
+ GroupDescription.Type type,
+ GroupBuckets buckets,
+ GroupKey appCookie,
+ Integer groupId,
+ ApplicationId appId) {
+ this.type = checkNotNull(type);
+ this.deviceId = checkNotNull(deviceId);
+ this.buckets = checkNotNull(buckets);
+ this.appCookie = appCookie;
+ this.givenGroupId = groupId;
+ this.appId = appId;
+ }
+
+ /**
+ * Constructor to be used by group subsystem internal components.
+ * Creates group description object from another object of same type.
+ *
+ * @param groupDesc group description object
+ *
+ */
+ public DefaultGroupDescription(GroupDescription groupDesc) {
+ this.type = groupDesc.type();
+ this.deviceId = groupDesc.deviceId();
+ this.buckets = groupDesc.buckets();
+ this.appCookie = groupDesc.appCookie();
+ this.appId = groupDesc.appId();
+ this.givenGroupId = groupDesc.givenGroupId();
+ }
+
+ /**
+ * Constructor to be used by group subsystem internal components.
+ * Creates group description object from the information retrieved
+ * from data plane.
+ *
+ * @param deviceId device identifier
+ * @param type type of the group
+ * @param buckets immutable list of group bucket
+ *
+ */
+ public DefaultGroupDescription(DeviceId deviceId,
+ GroupDescription.Type type,
+ GroupBuckets buckets) {
+ this(deviceId, type, buckets, null, null, null);
+ }
+
+ /**
+ * Returns type of a group object.
+ *
+ * @return GroupType group type
+ */
+ @Override
+ public GroupDescription.Type type() {
+ return this.type;
+ }
+
+ /**
+ * Returns device identifier on which this group object is created.
+ *
+ * @return DeviceId device identifier
+ */
+ @Override
+ public DeviceId deviceId() {
+ return this.deviceId;
+ }
+
+ /**
+ * Returns application identifier that has created this group object.
+ *
+ * @return ApplicationId application identifier
+ */
+ @Override
+ public ApplicationId appId() {
+ return this.appId;
+ }
+
+ /**
+ * Returns application cookie associated with a group object.
+ *
+ * @return GroupKey application cookie
+ */
+ @Override
+ public GroupKey appCookie() {
+ return this.appCookie;
+ }
+
+ /**
+ * Returns group buckets of a group.
+ *
+ * @return GroupBuckets immutable list of group bucket
+ */
+ @Override
+ public GroupBuckets buckets() {
+ return this.buckets;
+ }
+
+ /**
+ * Returns groupId passed in by application.
+ *
+ * @return Integer group Id passed in by caller. May be null if caller passed
+ * in null during GroupDescription creation.
+ */
+ @Override
+ public Integer givenGroupId() {
+ return this.givenGroupId;
+ }
+
+ @Override
+ /*
+ * The deviceId, type and buckets are used for hash.
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public int hashCode() {
+ return Objects.hash(deviceId, type, buckets);
+ }
+
+ @Override
+ /*
+ * The deviceId, type and buckets should be same.
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultGroupDescription) {
+ DefaultGroupDescription that = (DefaultGroupDescription) obj;
+ return Objects.equals(deviceId, that.deviceId) &&
+ Objects.equals(type, that.type) &&
+ Objects.equals(buckets, that.buckets);
+
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("deviceId", deviceId)
+ .add("type", type)
+ .add("buckets", buckets)
+ .add("appId", appId)
+ .add("givenGroupId", givenGroupId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupKey.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupKey.java
new file mode 100644
index 00000000..7f00ae70
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/DefaultGroupKey.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.group;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Arrays;
+
+/**
+ * Default implementation of group key interface.
+ */
+public class DefaultGroupKey implements GroupKey {
+
+ private final byte[] key;
+
+ public DefaultGroupKey(byte[] key) {
+ this.key = checkNotNull(key);
+ }
+
+ @Override
+ public byte[] key() {
+ return key;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof DefaultGroupKey)) {
+ return false;
+ }
+ DefaultGroupKey that = (DefaultGroupKey) o;
+ return (Arrays.equals(this.key, that.key));
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(this.key);
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/Group.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/Group.java
new file mode 100644
index 00000000..54407752
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/Group.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import org.onosproject.core.GroupId;
+
+/**
+ * ONOS representation of group that is stored in the system.
+ */
+public interface Group extends GroupDescription {
+ /**
+ * State of the group object in ONOS.
+ */
+ enum GroupState {
+ /**
+ * Group create request is queued as group AUDIT is in progress.
+ */
+ WAITING_AUDIT_COMPLETE,
+ /**
+ * Group create request is processed by ONOS and not yet
+ * received the confirmation from data plane.
+ */
+ PENDING_ADD,
+ /**
+ * Group is missing in data plane and retrying GROUP ADD request.
+ */
+ PENDING_ADD_RETRY,
+ /**
+ * Group is created in the data plane.
+ */
+ ADDED,
+ /**
+ * Group update request is processed by ONOS and not
+ * received the confirmation from data plane post which
+ * state moves to ADDED state.
+ */
+ PENDING_UPDATE,
+ /**
+ * Group delete request is processed by ONOS and not
+ * received the confirmation from data plane.
+ */
+ PENDING_DELETE
+ }
+
+ /**
+ * Returns group identifier associated with a group object.
+ *
+ * @return GroupId Group Identifier
+ */
+ GroupId id();
+
+ /**
+ * Returns current state of a group object.
+ *
+ * @return GroupState Group State
+ */
+ GroupState state();
+
+ /**
+ * Returns the number of milliseconds this group has been alive.
+ *
+ * @return number of millis
+ */
+ long life();
+
+ /**
+ * Returns the number of packets processed by this group.
+ *
+ * @return number of packets
+ */
+ long packets();
+
+ /**
+ * Returns the number of bytes processed by this group.
+ *
+ * @return number of bytes
+ */
+ long bytes();
+
+ /**
+ * Returns the number of flow rules or other groups reference this group.
+ *
+ * @return number of flow rules or other groups pointing to this group
+ */
+ long referenceCount();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
new file mode 100644
index 00000000..a503c154
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import org.onosproject.core.GroupId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.TrafficTreatment;
+
+/**
+ * Group Bucket definition. A default group Bucket is collection of
+ * Instructions that can be performed on a traffic flow. A failover
+ * group bucket is associated with a specific port or group that
+ * controls its liveness. A select group bucket contains optional
+ * weight field to define the weights among the buckets in the group.
+ */
+public interface GroupBucket {
+ /**
+ * Returns group type of the bucket.
+ *
+ * @return GroupType group type
+ */
+ GroupDescription.Type type();
+
+ /**
+ * Returns list of Traffic instructions that are part of the bucket.
+ *
+ * @return TrafficTreatment traffic instruction list
+ */
+ TrafficTreatment treatment();
+
+ /**
+ * Returns weight of select group bucket.
+ *
+ * @return short weight associated with a bucket
+ */
+ short weight();
+
+ /**
+ * Returns port number used for liveness detection for a
+ * failover bucket.
+ *
+ * @return PortNumber port number used for liveness detection
+ */
+ PortNumber watchPort();
+
+ /**
+ * Returns group identifier used for liveness detection for a
+ * failover bucket.
+ *
+ * @return GroupId group identifier to be used for liveness detection
+ */
+ GroupId watchGroup();
+
+ /**
+ * Returns the number of packets processed by this group bucket.
+ *
+ * @return number of packets
+ */
+ long packets();
+
+ /**
+ * Returns the number of bytes processed by this group bucket.
+ *
+ * @return number of bytes
+ */
+ long bytes();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
new file mode 100644
index 00000000..c0b5e5c6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Immutable collection of group bucket.
+ */
+public final class GroupBuckets {
+ private final List<GroupBucket> buckets;
+
+ /**
+ * Creates a immutable list of group bucket.
+ *
+ * @param buckets list of group bucket
+ */
+ public GroupBuckets(List<GroupBucket> buckets) {
+ this.buckets = ImmutableList.copyOf(checkNotNull(buckets));
+ }
+
+ /**
+ * Returns immutable list of group buckets.
+ *
+ * @return list of group bucket
+ */
+ public List<GroupBucket> buckets() {
+ return buckets;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ int combinedHash = 0;
+ for (GroupBucket bucket:buckets) {
+ combinedHash = combinedHash + bucket.hashCode();
+ }
+ result = 31 * result + combinedHash;
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof GroupBuckets) {
+ return (this.buckets.containsAll(((GroupBuckets) obj).buckets) &&
+ ((GroupBuckets) obj).buckets.containsAll(this.buckets));
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("buckets", buckets.toString())
+ .toString();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupDescription.java
new file mode 100644
index 00000000..671b9a54
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupDescription.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * ONOS representation of group description that is used to create
+ * a group. It contains immutable properties of a ONOS group construct
+ * such as "type", "DeviceId", "appCookie", "appId" and "buckets"
+ */
+public interface GroupDescription {
+ /**
+ * Types of the group supported by ONOS.
+ */
+ enum Type {
+ /**
+ * Load-balancing among different buckets in a group.
+ */
+ SELECT,
+ /**
+ * Single Bucket Group.
+ */
+ INDIRECT,
+ /**
+ * Multicast to all buckets in a group.
+ */
+ ALL,
+ /**
+ * Uses the first live bucket in a group.
+ */
+ FAILOVER
+ }
+
+ /**
+ * Returns type of a group object.
+ *
+ * @return GroupType group type
+ */
+ Type type();
+
+ /**
+ * Returns device identifier on which this group object is created.
+ *
+ * @return DeviceId device identifier
+ */
+ DeviceId deviceId();
+
+ /**
+ * Returns application identifier that has created this group object.
+ *
+ * @return ApplicationId application identifier
+ */
+ ApplicationId appId();
+
+ /**
+ * Returns application cookie associated with a group object.
+ *
+ * @return GroupKey application cookie
+ */
+ GroupKey appCookie();
+
+ /**
+ * Returns groupId passed in by caller.
+ *
+ * @return Integer group id passed in by caller. May be null if caller
+ * passed in null to let groupService determin the group id.
+ */
+ Integer givenGroupId();
+
+ /**
+ * Returns group buckets of a group.
+ *
+ * @return GroupBuckets immutable list of group bucket
+ */
+ GroupBuckets buckets();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java
new file mode 100644
index 00000000..45fbb3ed
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes group events.
+ */
+public class GroupEvent extends AbstractEvent<GroupEvent.Type, Group> {
+
+ /**
+ * Type of flow rule events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a new Group has been detected.
+ */
+ GROUP_ADDED,
+
+ /**
+ * Signifies that a Group has been removed.
+ */
+ GROUP_REMOVED,
+
+ /**
+ * Signifies that a Group has been updated.
+ */
+ GROUP_UPDATED,
+
+ /**
+ * Signifies that a request to create Group has failed.
+ */
+ GROUP_ADD_FAILED,
+
+ /**
+ * Signifies that a request to remove Group has failed.
+ */
+ GROUP_REMOVE_FAILED,
+
+ /**
+ * Signifies that a request to update Group has failed.
+ */
+ GROUP_UPDATE_FAILED,
+
+ // internal event between Manager <-> Store
+
+ /*
+ * Signifies that a request to create Group has been added to the store.
+ */
+ GROUP_ADD_REQUESTED,
+ /*
+ * Signifies that a request to update Group has been added to the store.
+ */
+ GROUP_UPDATE_REQUESTED,
+ /*
+ * Signifies that a request to delete Group has been added to the store.
+ */
+ GROUP_REMOVE_REQUESTED,
+
+
+ }
+
+ /**
+ * Creates an event of a given type and for the specified Group and the
+ * current time.
+ *
+ * @param type Group event type
+ * @param group event subject
+ */
+ public GroupEvent(Type type, Group group) {
+ super(type, group);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified Group and time.
+ *
+ * @param type Group event type
+ * @param group event subject
+ * @param time occurrence time
+ */
+ public GroupEvent(Type type, Group group, long time) {
+ super(type, group, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupKey.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupKey.java
new file mode 100644
index 00000000..a63bee27
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupKey.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Representation of generalized Key that would be used to store
+ * groups in &lt; Key, Value &gt; store. This key uses a generic
+ * byte array so that applications can associate their groups with
+ * any of their data by translating it into a byte array.
+ */
+public interface GroupKey {
+ /**
+ * Returns the byte representation of key.
+ *
+ * @return byte array
+ */
+ byte[] key();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupListener.java
new file mode 100644
index 00000000..349ce6a7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving Group related events.
+ */
+public interface GroupListener extends EventListener<GroupEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java
new file mode 100644
index 00000000..e4173b30
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperation.java
@@ -0,0 +1,181 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import org.onosproject.core.GroupId;
+
+/**
+ * Group operation definition to be used between core and provider
+ * layers of group subsystem.
+ *
+ */
+public final class GroupOperation {
+ private final Type opType;
+ private final GroupId groupId;
+ private final GroupDescription.Type groupType;
+ private final GroupBuckets buckets;
+
+ public enum Type {
+ /**
+ * Create a group in a device with the specified parameters.
+ */
+ ADD,
+ /**
+ * Modify a group in a device with the specified parameters.
+ */
+ MODIFY,
+ /**
+ * Delete a specified group.
+ */
+ DELETE
+ }
+
+ /**
+ * Group operation constructor with the parameters.
+ *
+ * @param opType group operation type
+ * @param groupId group Identifier
+ * @param groupType type of the group
+ * @param buckets immutable list of group buckets to be part of group
+ */
+ private GroupOperation(Type opType,
+ GroupId groupId,
+ GroupDescription.Type groupType,
+ GroupBuckets buckets) {
+ this.opType = checkNotNull(opType);
+ this.groupId = checkNotNull(groupId);
+ this.groupType = checkNotNull(groupType);
+ this.buckets = buckets;
+ }
+
+ /**
+ * Creates ADD group operation object.
+ *
+ * @param groupId group Identifier
+ * @param groupType type of the group
+ * @param buckets immutable list of group buckets to be part of group
+ * @return add group operation object
+ */
+ public static GroupOperation createAddGroupOperation(GroupId groupId,
+ GroupDescription.Type groupType,
+ GroupBuckets buckets) {
+ checkNotNull(buckets);
+ return new GroupOperation(Type.ADD, groupId, groupType, buckets);
+ }
+
+ /**
+ * Creates MODIFY group operation object.
+ *
+ * @param groupId group Identifier
+ * @param groupType type of the group
+ * @param buckets immutable list of group buckets to be part of group
+ * @return modify group operation object
+ */
+ public static GroupOperation createModifyGroupOperation(GroupId groupId,
+ GroupDescription.Type groupType,
+ GroupBuckets buckets) {
+ checkNotNull(buckets);
+ return new GroupOperation(Type.MODIFY, groupId, groupType, buckets);
+
+ }
+
+ /**
+ * Creates DELETE group operation object.
+ *
+ * @param groupId group Identifier
+ * @param groupType type of the group
+ * @return delete group operation object
+ */
+ public static GroupOperation createDeleteGroupOperation(GroupId groupId,
+ GroupDescription.Type groupType) {
+ return new GroupOperation(Type.DELETE, groupId, groupType, null);
+
+ }
+
+ /**
+ * Returns group operation type.
+ *
+ * @return GroupOpType group operation type
+ */
+ public Type opType() {
+ return this.opType;
+ }
+
+ /**
+ * Returns group identifier attribute of the operation.
+ *
+ * @return GroupId group identifier
+ */
+ public GroupId groupId() {
+ return this.groupId;
+ }
+
+ /**
+ * Returns group type attribute of the operation.
+ *
+ * @return GroupType group type
+ */
+ public GroupDescription.Type groupType() {
+ return this.groupType;
+ }
+
+ /**
+ * Returns group buckets associated with the operation.
+ *
+ * @return GroupBuckets group buckets
+ */
+ public GroupBuckets buckets() {
+ return this.buckets;
+ }
+
+ @Override
+ /*
+ * The deviceId, type and buckets are used for hash.
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public int hashCode() {
+ return (buckets != null) ? Objects.hash(groupId, opType, buckets) :
+ Objects.hash(groupId, opType);
+ }
+
+ @Override
+ /*
+ * The deviceId, type and buckets should be same.
+ *
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof GroupOperation) {
+ GroupOperation that = (GroupOperation) obj;
+ return Objects.equals(groupId, that.groupId) &&
+ Objects.equals(groupType, that.groupType) &&
+ Objects.equals(opType, that.opType) &&
+ Objects.equals(buckets, that.buckets);
+
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperations.java
new file mode 100644
index 00000000..bc03628d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupOperations.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Immutable collection of group operation to be used between
+ * core and provider layers of group subsystem.
+ *
+ */
+public final class GroupOperations {
+ private final List<GroupOperation> operations;
+
+ /**
+ * Creates a immutable list of group operation.
+ *
+ * @param operations list of group operation
+ */
+ public GroupOperations(List<GroupOperation> operations) {
+ this.operations = ImmutableList.copyOf(checkNotNull(operations));
+ }
+
+ /**
+ * Returns immutable list of group operation.
+ *
+ * @return list of group operation
+ */
+ public List<GroupOperation> operations() {
+ return operations;
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProvider.java
new file mode 100644
index 00000000..6757e669
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of group provider.
+ */
+public interface GroupProvider extends Provider {
+
+ /**
+ * Performs a batch of group operation in the specified device with the
+ * specified parameters.
+ *
+ * @param deviceId device identifier on which the batch of group
+ * operations to be executed
+ * @param groupOps immutable list of group operation
+ */
+ void performGroupOperation(DeviceId deviceId,
+ GroupOperations groupOps);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java
new file mode 100644
index 00000000..d45789db
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction for a group provider registry.
+ */
+public interface GroupProviderRegistry
+ extends ProviderRegistry<GroupProvider, GroupProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java
new file mode 100644
index 00000000..076de498
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import java.util.Collection;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderService;
+
+/**
+ * Service through which Group providers can inject information into
+ * the core.
+ */
+public interface GroupProviderService extends ProviderService<GroupProvider> {
+
+ /**
+ * Notifies core if any failure from data plane during group operations.
+ *
+ * @param deviceId the device ID
+ * @param operation offended group operation
+ */
+ void groupOperationFailed(DeviceId deviceId, GroupOperation operation);
+
+ /**
+ * Pushes the collection of group detected in the data plane along
+ * with statistics.
+ *
+ * @param deviceId device identifier
+ * @param groupEntries collection of group entries as seen in data plane
+ */
+ void pushGroupMetrics(DeviceId deviceId,
+ Collection<Group> groupEntries);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupService.java
new file mode 100644
index 00000000..4163248f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupService.java
@@ -0,0 +1,139 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Service for create/update/delete "group" in the devices.
+ * Flow entries can point to a "group" defined in the devices that enables
+ * to represent additional methods of forwarding like load-balancing or
+ * failover among different group of ports or multicast to all ports
+ * specified in a group.
+ * "group" can also be used for grouping common actions of different flows,
+ * so that in some scenarios only one group entry required to be modified
+ * for all the referencing flow entries instead of modifying all of them.
+ *
+ * This implements semantics of a distributed authoritative group store
+ * where the master copy of the groups lies with the controller and
+ * the devices hold only the 'cached' copy.
+ */
+public interface GroupService
+ extends ListenerService<GroupEvent, GroupListener> {
+
+ /**
+ * Creates a group in the specified device with the provided buckets.
+ * This API provides an option for application to associate a cookie
+ * while creating a group, so that applications can look-up the
+ * groups based on the cookies. These Groups will be retained by
+ * the core system and re-applied if any groups found missing in the
+ * device when it reconnects. This API would immediately return after
+ * submitting the request locally or to a remote Master controller
+ * instance. As a response to this API invocation, GROUP_ADDED or
+ * GROUP_ADD_FAILED notifications would be provided along with cookie
+ * depending on the result of the operation on the device in the
+ * data plane. The caller may also use "getGroup" API to get the
+ * Group object created as part of this request.
+ *
+ * @param groupDesc group creation parameters
+ *
+ */
+ void addGroup(GroupDescription groupDesc);
+
+ /**
+ * Returns a group object associated to an application cookie.
+ *
+ * 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
+ */
+ Group getGroup(DeviceId deviceId, GroupKey appCookie);
+
+ /**
+ * Appends 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
+ */
+ void addBucketsToGroup(DeviceId deviceId,
+ GroupKey oldCookie,
+ GroupBuckets buckets,
+ GroupKey newCookie,
+ ApplicationId appId);
+
+ /**
+ * Removes 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
+ */
+ void removeBucketsFromGroup(DeviceId deviceId,
+ GroupKey oldCookie,
+ GroupBuckets buckets,
+ GroupKey newCookie,
+ ApplicationId appId);
+
+ /**
+ * Deletes 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
+ */
+ void removeGroup(DeviceId deviceId, GroupKey appCookie, ApplicationId appId);
+
+ /**
+ * Retrieves 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
+ */
+ Iterable<Group> getGroups(DeviceId deviceId, ApplicationId appId);
+
+ /**
+ * Returns all groups associated with the given device.
+ *
+ * @param deviceId device ID to get groups for
+ * @return iterable of device's groups
+ */
+ Iterable<Group> getGroups(DeviceId deviceId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStore.java
new file mode 100644
index 00000000..8b6df5d9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStore.java
@@ -0,0 +1,175 @@
+/*
+ * 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;
+
+import java.util.Collection;
+
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Store;
+
+/**
+ * Manages inventory of groups per device; not intended for direct use.
+ */
+public interface GroupStore extends Store<GroupEvent, GroupStoreDelegate> {
+
+ enum UpdateType {
+ /**
+ * Modify existing group entry by adding provided information.
+ */
+ ADD,
+ /**
+ * Modify existing group by removing provided information from it.
+ */
+ REMOVE
+ }
+
+ /**
+ * Returns the number of groups for the specified device in the store.
+ *
+ * @param deviceId the device ID
+ * @return number of groups for the specified device
+ */
+ int getGroupCount(DeviceId deviceId);
+
+ /**
+ * Returns the groups associated with a device.
+ *
+ * @param deviceId the device ID
+ * @return the group entries
+ */
+ Iterable<Group> getGroups(DeviceId deviceId);
+
+ /**
+ * Returns the stored group entry.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ * @return a group associated with the key
+ */
+ Group getGroup(DeviceId deviceId, GroupKey appCookie);
+
+ /**
+ * Returns the stored group entry for an id.
+ *
+ * @param deviceId the device ID
+ * @param groupId the group identifier
+ * @return a group associated with the key
+ */
+ Group getGroup(DeviceId deviceId, GroupId groupId);
+
+ /**
+ * Stores a new group entry using the information from group description.
+ *
+ * @param groupDesc group description to be used to store group entry
+ */
+ void storeGroupDescription(GroupDescription groupDesc);
+
+ /**
+ * Updates the existing group entry with the information
+ * from group description.
+ *
+ * @param deviceId the device ID
+ * @param oldAppCookie the current group key
+ * @param type update type
+ * @param newBuckets group buckets for updates
+ * @param newAppCookie optional new group key
+ */
+ void updateGroupDescription(DeviceId deviceId,
+ GroupKey oldAppCookie,
+ UpdateType type,
+ GroupBuckets newBuckets,
+ GroupKey newAppCookie);
+
+ /**
+ * Triggers deleting the existing group entry.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ */
+ void deleteGroupDescription(DeviceId deviceId,
+ GroupKey appCookie);
+
+ /**
+ * Stores a new group entry, or updates an existing entry.
+ *
+ * @param group group entry
+ */
+ void addOrUpdateGroupEntry(Group group);
+
+ /**
+ * Removes the group entry from store.
+ *
+ * @param group group entry
+ */
+ void removeGroupEntry(Group group);
+
+ /**
+ * A group entry that is present in switch but not in the store.
+ *
+ * @param group group entry
+ */
+ void addOrUpdateExtraneousGroupEntry(Group group);
+
+ /**
+ * Remove the group entry from extraneous database.
+ *
+ * @param group group entry
+ */
+ void removeExtraneousGroupEntry(Group group);
+
+ /**
+ * Returns the extraneous groups associated with a device.
+ *
+ * @param deviceId the device ID
+ *
+ * @return the extraneous group entries
+ */
+ Iterable<Group> getExtraneousGroups(DeviceId deviceId);
+
+ /**
+ * Indicates the first group audit is completed.
+ *
+ * @param deviceId the device ID
+ * @param completed initial audit status
+ */
+ void deviceInitialAuditCompleted(DeviceId deviceId, boolean completed);
+
+ /**
+ * Retrieves the initial group audit status for a device.
+ *
+ * @param deviceId the device ID
+ *
+ * @return initial group audit status
+ */
+ boolean deviceInitialAuditStatus(DeviceId deviceId);
+
+ /**
+ * Indicates the group operations failed.
+ *
+ * @param deviceId the device ID
+ * @param operation the group operation failed
+ */
+ void groupOperationFailed(DeviceId deviceId, GroupOperation operation);
+
+ /**
+ * Submits the group metrics to store for a given device ID.
+ *
+ * @param deviceId the device ID
+ * @param groupEntries the group entries as received from southbound
+ */
+ void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStoreDelegate.java
new file mode 100644
index 00000000..308ebb1c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/GroupStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Group store delegate abstraction.
+ */
+public interface GroupStoreDelegate extends StoreDelegate<GroupEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
new file mode 100644
index 00000000..131875b3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Generic group bucket entry representation that is stored in a
+ * group object. A group bucket entry provides additional info of
+ * group bucket like statistics...etc
+ */
+public interface StoredGroupBucketEntry extends GroupBucket {
+ /**
+ * Sets number of packets processed by this group bucket entry.
+ *
+ * @param packets a long value
+ */
+ void setPackets(long packets);
+
+ /**
+ * Sets number of bytes processed by this group bucket entry.
+ *
+ * @param bytes a long value
+ */
+ void setBytes(long bytes);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java
new file mode 100644
index 00000000..47d36122
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/StoredGroupEntry.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+/**
+ * Interface that defines set methods for a group entry
+ * that is stored in the system.
+ */
+public interface StoredGroupEntry extends Group {
+
+ /**
+ * Sets the new state for this entry.
+ *
+ * @param newState new group entry state.
+ */
+ void setState(Group.GroupState newState);
+
+ /**
+ * Sets if group has transitioned to ADDED state for the first time.
+ * This is to differentiate state transitions "from PENDING_ADD to ADDED"
+ * and "from PENDING_UPDATE to ADDED". For internal use only.
+ *
+ * @param isGroupAddedFirstTime true if group moves to ADDED state
+ * for the first time.
+ */
+ void setIsGroupStateAddedFirstTime(boolean isGroupAddedFirstTime);
+
+ /**
+ * Returns the isGroupStateAddedFirstTime value. For internal use only.
+ *
+ * @return isGroupStateAddedFirstTime value
+ */
+ boolean isGroupStateAddedFirstTime();
+
+ /**
+ * Sets how long this entry has been entered in the system.
+ *
+ * @param life epoch time
+ */
+ void setLife(long life);
+
+ /**
+ * Sets number of packets processed by this group entry.
+ *
+ * @param packets a long value
+ */
+ void setPackets(long packets);
+
+ /**
+ * Sets number of bytes processed by this group entry.
+ *
+ * @param bytes a long value
+ */
+ void setBytes(long bytes);
+
+ /**
+ * Sets number of flow rules or groups referencing this group entry.
+ *
+ * @param referenceCount reference count
+ */
+ void setReferenceCount(long referenceCount);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/package-info.java
new file mode 100644
index 00000000..26528c48
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/group/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.
+ */
+
+/**
+ * Abstractions for interacting with device port groups.
+ */
+package org.onosproject.net.group; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java
new file mode 100644
index 00000000..1f05197a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.SparseAnnotations;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import com.google.common.collect.ImmutableSet;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of an immutable host description.
+ */
+public class DefaultHostDescription extends AbstractDescription
+ implements HostDescription {
+
+ private final MacAddress mac;
+ private final VlanId vlan;
+ private final HostLocation location;
+ private final Set<IpAddress> ip;
+
+ /**
+ * Creates a host description using the supplied information.
+ *
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param location host location
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultHostDescription(MacAddress mac, VlanId vlan,
+ HostLocation location,
+ SparseAnnotations... annotations) {
+ this(mac, vlan, location, Collections.<IpAddress>emptySet(),
+ annotations);
+ }
+
+ /**
+ * Creates a host description using the supplied information.
+ *
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param location host location
+ * @param ip host IP address
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultHostDescription(MacAddress mac, VlanId vlan,
+ HostLocation location, IpAddress ip,
+ SparseAnnotations... annotations) {
+ this(mac, vlan, location, ImmutableSet.of(ip), annotations);
+ }
+
+ /**
+ * Creates a host description using the supplied information.
+ *
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param location host location
+ * @param ip host IP addresses
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultHostDescription(MacAddress mac, VlanId vlan,
+ HostLocation location, Set<IpAddress> ip,
+ SparseAnnotations... annotations) {
+ super(annotations);
+ this.mac = mac;
+ this.vlan = vlan;
+ this.location = location;
+ this.ip = ImmutableSet.copyOf(ip);
+ }
+
+ @Override
+ public MacAddress hwAddress() {
+ return mac;
+ }
+
+ @Override
+ public VlanId vlan() {
+ return vlan;
+ }
+
+ @Override
+ public HostLocation location() {
+ return location;
+ }
+
+ @Override
+ public Set<IpAddress> ipAddress() {
+ return ip;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("mac", mac)
+ .add("vlan", vlan)
+ .add("location", location)
+ .add("ipAddress", ip)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostAdminService.java
new file mode 100644
index 00000000..d620fedb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostAdminService.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+
+/**
+ * Service for administering the inventory of end-station hosts.
+ */
+public interface HostAdminService extends HostService {
+
+ /**
+ * Removes the end-station host with the specified identifier.
+ *
+ * @param hostId host identifier
+ */
+ void removeHost(HostId hostId);
+
+ /**
+ * Binds IP and MAC addresses to the given connection point.
+ * <p>
+ * The addresses are added to the set of addresses already bound to the
+ * connection point.
+ *
+ * @param addresses address object containing addresses to add and the port
+ * to add them to
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ void bindAddressesToPort(PortAddresses addresses);
+
+ /**
+ * Removes the addresses contained in the given PortAddresses object from
+ * the set of addresses bound to the port.
+ *
+ * @param portAddresses set of addresses to remove and port to remove them
+ * from
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ void unbindAddressesFromPort(PortAddresses portAddresses);
+
+ /**
+ * Removes all address information for the given connection point.
+ *
+ * @param connectPoint the connection point to remove address information
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ void clearAddresses(ConnectPoint connectPoint);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostDescription.java
new file mode 100644
index 00000000..14c6f7ad
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostDescription.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.net.host;
+
+import java.util.Set;
+
+import org.onosproject.net.Description;
+import org.onosproject.net.HostLocation;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+/**
+ * Information describing host and its location.
+ */
+public interface HostDescription extends Description {
+
+ /**
+ * Returns the MAC address associated with this host (NIC).
+ *
+ * @return the MAC address of this host
+ */
+ MacAddress hwAddress();
+
+ /**
+ * Returns the VLAN associated with this host.
+ *
+ * @return the VLAN ID value
+ */
+ VlanId vlan();
+
+ /**
+ * Returns the location of the host on the network edge.
+ *
+ * @return the network location
+ */
+ HostLocation location();
+
+ /**
+ * Returns the IP address associated with this host's MAC.
+ *
+ * @return host IP address
+ */
+ Set<IpAddress> ipAddress();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
new file mode 100644
index 00000000..98329df0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.Host;
+
+/**
+ * Describes end-station host event.
+ */
+public class HostEvent extends AbstractEvent<HostEvent.Type, Host> {
+
+ /**
+ * Type of host events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a new host has been detected.
+ */
+ HOST_ADDED,
+
+ /**
+ * Signifies that a host has been removed.
+ */
+ HOST_REMOVED,
+
+ /**
+ * Signifies that host data changed, e.g. IP address
+ */
+ HOST_UPDATED,
+
+ /**
+ * Signifies that a host location has changed.
+ */
+ HOST_MOVED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified host and the
+ * current time.
+ *
+ * @param type host event type
+ * @param host event host subject
+ */
+ public HostEvent(Type type, Host host) {
+ super(type, host);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified host and time.
+ *
+ * @param type host event type
+ * @param host event host subject
+ * @param time occurrence time
+ */
+ public HostEvent(Type type, Host host, long time) {
+ super(type, host, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostListener.java
new file mode 100644
index 00000000..2eef7592
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving end-station host related events.
+ */
+public interface HostListener extends EventListener<HostEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProvider.java
new file mode 100644
index 00000000..0270996b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProvider.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.host;
+
+import org.onosproject.net.Host;
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Provider of information about hosts and their location on the network.
+ */
+public interface HostProvider extends Provider {
+
+ /**
+ * Triggers an asynchronous probe of the specified host, intended to
+ * determine whether the host is present or not. An indirect result of this
+ * should be invocation of {@link org.onosproject.net.host.HostProviderService#hostDetected}
+ * or {@link org.onosproject.net.host.HostProviderService#hostVanished}
+ * at some later point in time.
+ *
+ * @param host host to probe
+ */
+ void triggerProbe(Host host);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderRegistry.java
new file mode 100644
index 00000000..8ab600c2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction of a host provider registry.
+ */
+public interface HostProviderRegistry
+ extends ProviderRegistry<HostProvider, HostProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java
new file mode 100644
index 00000000..8678a297
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.onosproject.net.HostId;
+import org.onosproject.net.provider.ProviderService;
+
+/**
+ * Means of conveying host information to the core.
+ */
+public interface HostProviderService extends ProviderService<HostProvider> {
+
+ /**
+ * Notifies the core when a host has been detected on a network along with
+ * information that identifies the host location.
+ *
+ * @param hostId id of the host that been detected
+ * @param hostDescription description of host and its location
+ */
+ void hostDetected(HostId hostId, HostDescription hostDescription);
+
+ /**
+ * Notifies the core when a host is no longer detected on a network.
+ *
+ * @param hostId id of the host that vanished
+ */
+ void hostVanished(HostId hostId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostService.java
new file mode 100644
index 00000000..be114f05
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostService.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+
+import java.util.Set;
+
+/**
+ * Service for interacting with the inventory of end-station hosts.
+ */
+public interface HostService
+ extends ListenerService<HostEvent, HostListener> {
+
+ /**
+ * Returns the number of end-station hosts known to the system.
+ *
+ * @return number of end-station hosts
+ */
+ int getHostCount();
+
+ /**
+ * Returns a collection of all end-station hosts.
+ *
+ * @return collection of hosts
+ */
+ Iterable<Host> getHosts();
+
+ /**
+ * Returns the host with the specified identifier.
+ *
+ * @param hostId host identifier
+ * @return host or null if one with the given identifier is not known
+ */
+ Host getHost(HostId hostId);
+
+ /**
+ * Returns the set of hosts that belong to the specified VLAN.
+ *
+ * @param vlanId vlan identifier
+ * @return set of hosts in the given vlan id
+ */
+ Set<Host> getHostsByVlan(VlanId vlanId);
+
+ /**
+ * Returns the set of hosts that have the specified MAC address.
+ *
+ * @param mac mac address
+ * @return set of hosts with the given mac
+ */
+ Set<Host> getHostsByMac(MacAddress mac);
+
+ /**
+ * Returns the set of hosts that have the specified IP address.
+ *
+ * @param ip ip address
+ * @return set of hosts with the given IP
+ */
+ Set<Host> getHostsByIp(IpAddress ip);
+
+ // TODO: consider adding Host getHostByIp(IpAddress ip, VlanId vlan);
+
+ /**
+ * Returns the set of hosts whose most recent location is the specified
+ * connection point.
+ *
+ * @param connectPoint connection point
+ * @return set of hosts connected to the connection point
+ */
+ Set<Host> getConnectedHosts(ConnectPoint connectPoint);
+
+ /**
+ * Returns the set of hosts whose most recent location is the specified
+ * infrastructure device.
+ *
+ * @param deviceId device identifier
+ * @return set of hosts connected to the device
+ */
+ Set<Host> getConnectedHosts(DeviceId deviceId);
+
+ /**
+ * Requests the host service to monitor hosts with the given IP address and
+ * notify listeners of changes.
+ *
+ * @param ip IP address of the host to monitor
+ */
+ void startMonitoringIp(IpAddress ip);
+
+ /**
+ * Stops the host service from monitoring an IP address.
+ *
+ * @param ip IP address to stop monitoring
+ */
+ // TODO clients can cancel other client's requests
+ void stopMonitoringIp(IpAddress ip);
+
+ /**
+ * Requests the host service to resolve the MAC address for the given IP
+ * address. This will trigger a notification to the host listeners if the MAC
+ * address is found.
+ *
+ * @param ip IP address to find the MAC address for
+ */
+ void requestMac(IpAddress ip);
+
+ /**
+ * Returns the addresses information for all connection points.
+ *
+ * @return the set of address bindings for all connection points
+ * @deprecated in Drake release: use InterfaceService instead
+ */
+ @Deprecated
+ Set<PortAddresses> getAddressBindings();
+
+ /**
+ * Retrieves the addresses that have been bound to the given connection
+ * point.
+ *
+ * @param connectPoint the connection point to retrieve address bindings for
+ * @return addresses bound to the port
+ * @deprecated in Drake release: use InterfaceService instead
+ */
+ @Deprecated
+ Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStore.java
new file mode 100644
index 00000000..ca11a942
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStore.java
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Manages inventory of end-station hosts; not intended for direct use.
+ */
+public interface HostStore extends Store<HostEvent, HostStoreDelegate> {
+
+ /**
+ * Creates a new host or updates the existing one based on the specified
+ * description.
+ *
+ * @param providerId provider identification
+ * @param hostId host identification
+ * @param hostDescription host description data
+ * @return appropriate event or null if no change resulted
+ */
+ HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
+ HostDescription hostDescription);
+
+ // FIXME: API to remove only IpAddress is missing
+ /**
+ * Removes the specified host from the inventory.
+ *
+ * @param hostId host identification
+ * @return remove event or null if host was not found
+ */
+ HostEvent removeHost(HostId hostId);
+
+ /**
+ * Returns the number of hosts in the store.
+ *
+ * @return host count
+ */
+ int getHostCount();
+
+ /**
+ * Returns a collection of all hosts in the store.
+ *
+ * @return iterable collection of all hosts
+ */
+ Iterable<Host> getHosts();
+
+ /**
+ * Returns the host with the specified identifer.
+ *
+ * @param hostId host identification
+ * @return host or null if not found
+ */
+ Host getHost(HostId hostId);
+
+ /**
+ * Returns the set of all hosts within the specified VLAN.
+ *
+ * @param vlanId vlan id
+ * @return set of hosts in the vlan
+ */
+ Set<Host> getHosts(VlanId vlanId);
+
+ /**
+ * Returns the set of hosts with the specified MAC address.
+ *
+ * @param mac mac address
+ * @return set of hosts with the given mac
+ */
+ Set<Host> getHosts(MacAddress mac);
+
+ /**
+ * Returns the set of hosts with the specified IP address.
+ *
+ * @param ip ip address
+ * @return set of hosts with the given IP
+ */
+ Set<Host> getHosts(IpAddress ip);
+
+ /**
+ * Returns the set of hosts whose location falls on the given connection point.
+ *
+ * @param connectPoint connection point
+ * @return set of hosts
+ */
+ Set<Host> getConnectedHosts(ConnectPoint connectPoint);
+
+ /**
+ * Returns the set of hosts whose location falls on the given device.
+ *
+ * @param deviceId infrastructure device identifier
+ * @return set of hosts
+ */
+ Set<Host> getConnectedHosts(DeviceId deviceId);
+
+ /**
+ * Updates the address information for a given port. The given address
+ * information is added to any previously held information for the port.
+ *
+ * @param addresses the port and address information
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ void updateAddressBindings(PortAddresses addresses);
+
+ /**
+ * Removes the given addresses from the set of address information held for
+ * a port.
+ *
+ * @param addresses the port and address information
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ void removeAddressBindings(PortAddresses addresses);
+
+ /**
+ * Removes any previously stored address information for a given connection
+ * point.
+ *
+ * @param connectPoint the connection point
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ void clearAddressBindings(ConnectPoint connectPoint);
+
+ /**
+ * Returns the address bindings stored for all connection points.
+ *
+ * @return the set of address bindings
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ Set<PortAddresses> getAddressBindings();
+
+ /**
+ * Returns the address bindings for a particular connection point.
+ *
+ * @param connectPoint the connection point to return address information
+ * for
+ * @return address information for the connection point
+ * @deprecated in Drake release: address info now stored in InterfaceService
+ */
+ @Deprecated
+ Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java
new file mode 100644
index 00000000..efc84232
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Infrastructure link store delegate abstraction.
+ */
+public interface HostStoreDelegate extends StoreDelegate<HostEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/InterfaceIpAddress.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/InterfaceIpAddress.java
new file mode 100644
index 00000000..2f53df50
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/InterfaceIpAddress.java
@@ -0,0 +1,192 @@
+/*
+ * 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;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a single IP address information on an interface.
+ *
+ * TODO:
+ * - Add computation for the default broadcast address if it is not
+ * specified
+ * - Add explicit checks that each IP address or prefix belong to the
+ * same IP version: IPv4/IPv6.
+ * - Inside the copy constructor we should use copy constructors for each
+ * field
+ */
+public class InterfaceIpAddress {
+ private final IpAddress ipAddress;
+ private final IpPrefix subnetAddress;
+ private final IpAddress broadcastAddress;
+ private final IpAddress peerAddress;
+
+ /**
+ * Copy constructor.
+ *
+ * @param other the object to copy from
+ */
+ public InterfaceIpAddress(InterfaceIpAddress other) {
+ // TODO: we should use copy constructors for each field
+ this.ipAddress = other.ipAddress;
+ this.subnetAddress = other.subnetAddress;
+ this.broadcastAddress = other.broadcastAddress;
+ this.peerAddress = other.peerAddress;
+ }
+
+ /**
+ * Constructor for a given IP address and a subnet address.
+ *
+ * @param ipAddress the IP address
+ * @param subnetAddress the IP subnet address
+ */
+ public InterfaceIpAddress(IpAddress ipAddress, IpPrefix subnetAddress) {
+ this.ipAddress = checkNotNull(ipAddress);
+ this.subnetAddress = checkNotNull(subnetAddress);
+ // TODO: Recompute the default broadcast address from the subnet
+ // address
+ this.broadcastAddress = null;
+ this.peerAddress = null;
+ }
+
+ /**
+ * Constructor for a given IP address and a subnet address.
+ *
+ * @param ipAddress the IP address
+ * @param subnetAddress the IP subnet address
+ * @param broadcastAddress the IP broadcast address. It can be used
+ * to specify non-default broadcast address
+ */
+ public InterfaceIpAddress(IpAddress ipAddress, IpPrefix subnetAddress,
+ IpAddress broadcastAddress) {
+ this.ipAddress = checkNotNull(ipAddress);
+ this.subnetAddress = checkNotNull(subnetAddress);
+ this.broadcastAddress = broadcastAddress;
+ this.peerAddress = null;
+ }
+
+ /**
+ * Constructor for a given IP address and a subnet address.
+ *
+ * @param ipAddress the IP address
+ * @param subnetAddress the IP subnet address
+ * @param broadcastAddress the IP broadcast address. It can be used
+ * to specify non-default broadcast address. It should be null for
+ * point-to-point interfaces with a peer address
+ * @param peerAddress the peer IP address for point-to-point interfaces
+ */
+ public InterfaceIpAddress(IpAddress ipAddress, IpPrefix subnetAddress,
+ IpAddress broadcastAddress,
+ IpAddress peerAddress) {
+ this.ipAddress = checkNotNull(ipAddress);
+ this.subnetAddress = checkNotNull(subnetAddress);
+ this.broadcastAddress = broadcastAddress;
+ this.peerAddress = peerAddress;
+ }
+
+ /**
+ * Gets the IP address.
+ *
+ * @return the IP address
+ */
+ public IpAddress ipAddress() {
+ return ipAddress;
+ }
+
+ /**
+ * Gets the IP subnet address.
+ *
+ * @return the IP subnet address
+ */
+ public IpPrefix subnetAddress() {
+ return subnetAddress;
+ }
+
+ /**
+ * Gets the subnet IP broadcast address.
+ *
+ * @return the subnet IP broadcast address
+ */
+ public IpAddress broadcastAddress() {
+ return broadcastAddress;
+ }
+
+ /**
+ * Gets the IP point-to-point interface peer address.
+ *
+ * @return the IP point-to-point interface peer address
+ */
+ public IpAddress peerAddress() {
+ return peerAddress;
+ }
+
+ /**
+ * Converts a CIDR string literal to an interface IP address.
+ * E.g. 10.0.0.1/24
+ *
+ * @param value an IP address value in string form
+ * @return an interface IP address
+ * @throws IllegalArgumentException if the argument is invalid
+ */
+ public static InterfaceIpAddress valueOf(String value) {
+ String[] splits = value.split("/");
+ checkArgument(splits.length == 2, "Invalid IP address and prefix length format");
+
+ // NOTE: IpPrefix will mask-out the bits after the prefix length.
+ IpPrefix subnet = IpPrefix.valueOf(value);
+ IpAddress addr = IpAddress.valueOf(splits[0]);
+ return new InterfaceIpAddress(addr, subnet);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof InterfaceIpAddress)) {
+ return false;
+ }
+ InterfaceIpAddress otherAddr = (InterfaceIpAddress) other;
+
+ return Objects.equals(this.ipAddress, otherAddr.ipAddress)
+ && Objects.equals(this.subnetAddress, otherAddr.subnetAddress)
+ && Objects.equals(this.broadcastAddress,
+ otherAddr.broadcastAddress)
+ && Objects.equals(this.peerAddress, otherAddr.peerAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipAddress, subnetAddress, broadcastAddress,
+ peerAddress);
+ }
+
+ @Override
+ public String toString() {
+ /*return toStringHelper(this).add("ipAddress", ipAddress)
+ .add("subnetAddress", subnetAddress)
+ .add("broadcastAddress", broadcastAddress)
+ .add("peerAddress", peerAddress)
+ .omitNullValues().toString();*/
+ return ipAddress.toString() + "/" + subnetAddress.prefixLength();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/PortAddresses.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/PortAddresses.java
new file mode 100644
index 00000000..74f22ae9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/PortAddresses.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents address information bound to a port.
+ */
+public final class PortAddresses {
+
+ private final ConnectPoint connectPoint;
+ private final Set<InterfaceIpAddress> ipAddresses;
+ private final MacAddress macAddress;
+ private final VlanId vlan;
+
+ /**
+ * Constructs a PortAddresses object for the given connection point, with a
+ * set of IP addresses and a MAC address. Both address parameters are
+ * optional and can be set to null.
+ *
+ * @param connectPoint the connection point these addresses are for
+ * @param ipAddresses a set of interface IP addresses
+ * @param mac a MAC address
+ * @param vlan a VLAN ID
+ */
+ public PortAddresses(ConnectPoint connectPoint,
+ Set<InterfaceIpAddress> ipAddresses, MacAddress mac, VlanId vlan) {
+ this.connectPoint = connectPoint;
+ this.ipAddresses = (ipAddresses == null) ?
+ Collections.<InterfaceIpAddress>emptySet()
+ : new HashSet<>(ipAddresses);
+ this.macAddress = mac;
+ this.vlan = vlan;
+ }
+
+ /**
+ * Returns the connection point this address information is bound to.
+ *
+ * @return the connection point
+ */
+ public ConnectPoint connectPoint() {
+ return connectPoint;
+ }
+
+ /**
+ * Returns the set of interface IP addresses.
+ *
+ * @return the interface IP addresses
+ */
+ public Set<InterfaceIpAddress> ipAddresses() {
+ return ipAddresses;
+ }
+
+ /**
+ * Returns the MAC address.
+ *
+ * @return the MAC address
+ */
+ public MacAddress mac() {
+ return macAddress;
+ }
+
+ /**
+ * Returns the VLAN ID.
+ *
+ * @return the VLAN ID
+ */
+ public VlanId vlan() {
+ return vlan;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof PortAddresses)) {
+ return false;
+ }
+
+ PortAddresses otherPa = (PortAddresses) other;
+
+ return Objects.equals(this.connectPoint, otherPa.connectPoint)
+ && Objects.equals(this.ipAddresses, otherPa.ipAddresses)
+ && Objects.equals(this.macAddress, otherPa.macAddress)
+ && Objects.equals(this.vlan, otherPa.vlan);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(connectPoint, ipAddresses, macAddress, vlan);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("connect-point", connectPoint)
+ .add("ip-addresses", ipAddresses)
+ .add("mac-address", macAddress)
+ .add("vlan", vlan)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/package-info.java
new file mode 100644
index 00000000..4f2bc7c4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/host/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.
+ */
+
+/**
+ * End-station host model &amp; related services API definitions.
+ */
+package org.onosproject.net.host;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java
new file mode 100644
index 00000000..7caee3e8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java
@@ -0,0 +1,187 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetworkResource;
+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 java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of connectivity intent for traffic matching some criteria.
+ */
+@Beta
+public abstract class ConnectivityIntent extends Intent {
+
+ // TODO: other forms of intents should be considered for this family:
+ // point-to-point with constraints (waypoints/obstacles)
+ // multi-to-single point with constraints (waypoints/obstacles)
+ // single-to-multi point with constraints (waypoints/obstacles)
+ // concrete path (with alternate)
+ // ...
+
+ private final TrafficSelector selector;
+ private final TrafficTreatment treatment;
+ private final List<Constraint> constraints;
+
+ /**
+ * Creates a connectivity intent that matches on the specified selector
+ * and applies the specified treatment.
+ * <p>
+ * Path will be optimized based on the first constraint if one is given.
+ * </p>
+ *
+ * @param appId application identifier
+ * @param key explicit key to use for intent
+ * @param resources required network resources (optional)
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param constraints optional prioritized list of constraints
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if the selector or treatment is null
+ */
+ protected ConnectivityIntent(ApplicationId appId,
+ Key key,
+ Collection<NetworkResource> resources,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, resources, priority);
+ this.selector = checkNotNull(selector);
+ this.treatment = checkNotNull(treatment);
+ this.constraints = checkNotNull(constraints);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected ConnectivityIntent() {
+ super();
+ this.selector = null;
+ this.treatment = null;
+ this.constraints = Collections.emptyList();
+ }
+
+ /**
+ * Abstract builder for connectivity intents.
+ */
+ public abstract static class Builder extends Intent.Builder {
+ protected TrafficSelector selector = DefaultTrafficSelector.emptySelector();
+ protected TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+ protected List<Constraint> constraints = ImmutableList.of();
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+
+ /**
+ * Sets the traffic selector for the intent that will be built.
+ *
+ * @param selector selector to use for built intent
+ * @return this builder
+ */
+ public Builder selector(TrafficSelector selector) {
+ this.selector = selector;
+ return this;
+ }
+
+ /**
+ * Sets the traffic treatment for the intent that will be built.
+ *
+ * @param treatment treatment to use for built intent
+ * @return this builder
+ */
+ public Builder treatment(TrafficTreatment treatment) {
+ this.treatment = treatment;
+ return this;
+ }
+
+ /**
+ * Sets the constraints for the intent that will be built.
+ *
+ * @param constraints constraints to use for built intent
+ * @return this builder
+ */
+ public Builder constraints(List<Constraint> constraints) {
+ this.constraints = ImmutableList.copyOf(constraints);
+ return this;
+ }
+ }
+
+
+ /**
+ * Returns the match specifying the type of traffic.
+ *
+ * @return traffic match
+ */
+ public TrafficSelector selector() {
+ return selector;
+ }
+
+ /**
+ * Returns the action applied to the traffic.
+ *
+ * @return applied action
+ */
+ public TrafficTreatment treatment() {
+ return treatment;
+ }
+
+ /**
+ * Returns the set of connectivity constraints.
+ *
+ * @return list of intent constraints
+ */
+ public List<Constraint> constraints() {
+ return constraints;
+ }
+
+ /**
+ * Produces a collection of network resources from the given links.
+ *
+ * @param links collection of links
+ * @return collection of link resources
+ */
+ protected static Collection<NetworkResource> resources(Collection<Link> links) {
+ return ImmutableSet.copyOf(links);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Constraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Constraint.java
new file mode 100644
index 00000000..03acf17c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Constraint.java
@@ -0,0 +1,53 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+/**
+ * Representation of a connectivity constraint capable of evaluating a link
+ * and determining the cost of traversing that link in the context of this
+ * constraint.
+ */
+@Beta
+public interface Constraint {
+
+ // TODO: Consider separating cost vs viability.
+
+ /**
+ * Evaluates the specified link and provides the cost for its traversal.
+ *
+ * @param link link to be evaluated
+ * @param resourceService resource service for validating availability of
+ * link resources
+ * @return cost of link traversal
+ */
+ double cost(Link link, LinkResourceService resourceService);
+
+ /**
+ * Validates that the specified path satisfies the constraint.
+ *
+ * @param path path to be validated
+ * @param resourceService resource service for validating availability of
+ * link resources
+ * @return cost of link traversal
+ */
+ boolean validate(Path path, LinkResourceService resourceService);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
new file mode 100644
index 00000000..0646a003
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.flow.FlowRule;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An intent that enables to tell flow level operation.
+ * This instance holds a collection of flow rules that may be executed in parallel.
+ */
+@Beta
+public class FlowRuleIntent extends Intent {
+
+ private final Collection<FlowRule> flowRules;
+
+ /**
+ * Creates an flow rule intent with the specified flow rules to be set.
+ *
+ * @param appId application id
+ * @param flowRules flow rules to be set.
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ public FlowRuleIntent(ApplicationId appId, List<FlowRule> flowRules) {
+ this(appId, null, flowRules, Collections.emptyList());
+ }
+
+ /**
+ * Creates a flow rule intent with the specified flow rules and resources.
+ *
+ * @param appId application id
+ * @param flowRules flow rules to be set
+ * @param resources network resource to be set
+ */
+ public FlowRuleIntent(ApplicationId appId, List<FlowRule> flowRules, Collection<NetworkResource> resources) {
+ this(appId, null, flowRules, resources);
+ }
+
+ /**
+ * Creates an flow rule intent with the specified key, flow rules to be set, and
+ * required network resources.
+ *
+ * @param appId application id
+ * @param key key
+ * @param flowRules flow rules
+ * @param resources network resources
+ */
+ public FlowRuleIntent(ApplicationId appId, Key key, Collection<FlowRule> flowRules,
+ Collection<NetworkResource> resources) {
+ super(appId, key, resources, DEFAULT_INTENT_PRIORITY);
+ this.flowRules = ImmutableList.copyOf(checkNotNull(flowRules));
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected FlowRuleIntent() {
+ super();
+ this.flowRules = null;
+ }
+
+ /**
+ * Returns a collection of flow rules to be set.
+ *
+ * @return a collection of flow rules
+ */
+ public Collection<FlowRule> flowRules() {
+ return flowRules;
+ }
+
+ @Override
+ public boolean isInstallable() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("resources", resources())
+ .add("flowRule", flowRules)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/HostToHostIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/HostToHostIntent.java
new file mode 100644
index 00000000..bd4219ad
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/HostToHostIntent.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.intent;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of end-station to end-station bidirectional connectivity.
+ */
+@Beta
+public final class HostToHostIntent extends ConnectivityIntent {
+
+ private final HostId one;
+ private final HostId two;
+
+ /**
+ * Returns a new host to host intent builder.
+ *
+ * @return host to host intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a host to host intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ HostId one;
+ HostId two;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the first host of the intent that will be built.
+ *
+ * @param one first host
+ * @return this builder
+ */
+ public Builder one(HostId one) {
+ this.one = one;
+ return this;
+ }
+
+ /**
+ * Sets the second host of the intent that will be built.
+ *
+ * @param two second host
+ * @return this builder
+ */
+ public Builder two(HostId two) {
+ this.two = two;
+ return this;
+ }
+
+ /**
+ * Builds a host to host intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public HostToHostIntent build() {
+
+ return new HostToHostIntent(
+ appId,
+ key,
+ one,
+ two,
+ selector,
+ treatment,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+ /**
+ * Creates a new host-to-host intent with the supplied host pair.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ * @param one first host
+ * @param two second host
+ * @param selector action
+ * @param treatment ingress port
+ * @param constraints optional prioritized list of path selection constraints
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if {@code one} or {@code two} is null.
+ */
+ private HostToHostIntent(ApplicationId appId, Key key,
+ HostId one, HostId two,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, ImmutableSet.of(one, two), selector, treatment,
+ constraints, priority);
+
+ // TODO: consider whether the case one and two are same is allowed
+ this.one = checkNotNull(one);
+ this.two = checkNotNull(two);
+
+ }
+
+ /**
+ * Returns identifier of the first host.
+ *
+ * @return first host identifier
+ */
+ public HostId one() {
+ return one;
+ }
+
+ /**
+ * Returns identifier of the second host.
+ *
+ * @return second host identifier
+ */
+ public HostId two() {
+ return two;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("constraints", constraints())
+ .add("one", one)
+ .add("two", two)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Intent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Intent.java
new file mode 100644
index 00000000..077fd895
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Intent.java
@@ -0,0 +1,218 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.NetworkResource;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Abstraction of an application level intent.
+ * <p>
+ * Make sure that an Intent should be immutable when a new type is defined.
+ * </p>
+ */
+@Beta
+public abstract class Intent {
+
+ private final IntentId id;
+
+ private final ApplicationId appId;
+ private final Key key;
+
+ private final int priority;
+ public static final int DEFAULT_INTENT_PRIORITY = 100;
+ public static final int MAX_PRIORITY = (1 << 16) - 1;
+ public static final int MIN_PRIORITY = 1;
+
+ private final Collection<NetworkResource> resources;
+
+ private static IdGenerator idGenerator;
+
+ /**
+ * Constructor for serializer.
+ */
+ protected Intent() {
+ this.id = null;
+ this.appId = null;
+ this.key = null;
+ this.resources = null;
+ this.priority = DEFAULT_INTENT_PRIORITY;
+ }
+
+ /**
+ * Creates a new intent.
+ *
+ * @param appId application identifier
+ * @param key optional key
+ * @param resources required network resources (optional)
+ * @param priority flow rule priority
+ */
+ protected Intent(ApplicationId appId,
+ Key key,
+ Collection<NetworkResource> resources,
+ int priority) {
+ checkState(idGenerator != null, "Id generator is not bound.");
+ checkArgument(priority <= MAX_PRIORITY && priority >= MIN_PRIORITY);
+ this.id = IntentId.valueOf(idGenerator.getNewId());
+ this.appId = checkNotNull(appId, "Application ID cannot be null");
+ this.key = (key != null) ? key : Key.of(id.fingerprint(), appId);
+ this.priority = priority;
+ this.resources = checkNotNull(resources);
+ }
+
+ /**
+ * Abstract builder for intents.
+ */
+ public abstract static class Builder {
+ protected ApplicationId appId;
+ protected Key key;
+ protected int priority = Intent.DEFAULT_INTENT_PRIORITY;
+
+ /**
+ * Sets the application id for the intent that will be built.
+ *
+ * @param appId application id to use for built intent
+ * @return this builder
+ */
+ public Builder appId(ApplicationId appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ /**
+ * Sets the key for the intent that will be built.
+ *
+ * @param key key to use for built intent
+ * @return this builder
+ */
+ public Builder key(Key key) {
+ this.key = key;
+ return this;
+ }
+
+ /**
+ * Sets the priority for the intent that will be built.
+ *
+ * @param priority priority to use for built intent
+ * @return this builder
+ */
+ public Builder priority(int priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ }
+
+ /**
+ * Returns the intent identifier.
+ *
+ * @return intent fingerprint
+ */
+ public IntentId id() {
+ return id;
+ }
+
+ /**
+ * Returns the identifier of the application that requested the intent.
+ *
+ * @return application identifier
+ */
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ /**
+ * Returns the priority of the intent.
+ *
+ * @return intent priority
+ */
+ public int priority() {
+ return priority;
+ }
+
+ /**
+ * Returns the collection of resources required for this intent.
+ *
+ * @return collection of resources; may be null
+ */
+ public Collection<NetworkResource> resources() {
+ return resources;
+ }
+
+ /**
+ * Indicates whether or not the intent is installable.
+ *
+ * @return true if installable
+ */
+ public boolean isInstallable() {
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Intent other = (Intent) obj;
+ return this.id().equals(other.id());
+ }
+
+ /**
+ * Binds an id generator for unique intent id generation.
+ *
+ * Note: A generator cannot be bound if there is already a generator bound.
+ *
+ * @param newIdGenerator id generator
+ */
+ public static void bindIdGenerator(IdGenerator newIdGenerator) {
+ checkState(idGenerator == null, "Id generator is already bound.");
+ idGenerator = checkNotNull(newIdGenerator);
+ }
+
+ /**
+ * Unbinds an id generator.
+ *
+ * Note: The caller must provide the old id generator to succeed.
+ *
+ * @param oldIdGenerator the current id generator
+ */
+ public static void unbindIdGenerator(IdGenerator oldIdGenerator) {
+ if (Objects.equals(idGenerator, oldIdGenerator)) {
+ idGenerator = null;
+ }
+ }
+
+ public Key key() {
+ return key;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentBatchDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentBatchDelegate.java
new file mode 100644
index 00000000..e4babfb1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentBatchDelegate.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Collection;
+
+/**
+ * Facade for receiving notifications from the intent batch service.
+ */
+@Beta
+public interface IntentBatchDelegate {
+
+ /**
+ * Submits the specified batch of intent operations for processing.
+ *
+ * @param operations batch of operations
+ */
+ void execute(Collection<IntentData> operations);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentClockService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentClockService.java
new file mode 100644
index 00000000..d0dbacf8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentClockService.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.store.Timestamp;
+
+/**
+ * Logical clock service that issues per-intent timestamps.
+ */
+@Beta
+public interface IntentClockService {
+
+ /**
+ * Returns a new timestamp for the specified intent.
+ *
+ * @param intentId identifier for the intent.
+ * @return timestamp
+ */
+ Timestamp getTimestamp(IntentId intentId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
new file mode 100644
index 00000000..9a059be8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onosproject.net.resource.link.LinkResourceAllocations;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Abstraction of a compiler which is capable of taking an intent
+ * and translating it to other, potentially installable, intents.
+ *
+ * @param <T> the type of intent
+ */
+@Beta
+public interface IntentCompiler<T extends Intent> {
+ /**
+ * Compiles the specified intent into other intents.
+ *
+ * @param intent intent to be compiled
+ * @param installable previously compilation result; optional
+ * @param resources previously allocated resources; optional
+ * @return list of resulting intents
+ * @throws IntentException if issues are encountered while compiling the intent
+ */
+ List<Intent> compile(T intent, List<Intent> installable,
+ Set<LinkResourceAllocations> resources);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentData.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
new file mode 100644
index 00000000..e24e14e0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
@@ -0,0 +1,327 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.Timestamp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.*;
+
+/**
+ * A wrapper class that contains an intents, its state, and other metadata for
+ * internal use.
+ */
+@Beta
+public class IntentData { //FIXME need to make this "immutable"
+ // manager should be able to mutate a local copy while processing
+
+ private static final Logger log = LoggerFactory.getLogger(IntentData.class);
+
+ private final Intent intent;
+
+ private final IntentState request; //TODO perhaps we want a full fledged object for requests
+ private IntentState state;
+ private Timestamp version;
+ private NodeId origin;
+ private int errorCount;
+
+ private List<Intent> installables;
+
+ /**
+ * Creates a new intent data object.
+ *
+ * @param intent intent this metadata references
+ * @param state intent state
+ * @param version version of the intent for this key
+ */
+ public IntentData(Intent intent, IntentState state, Timestamp version) {
+ this.intent = intent;
+ this.state = state;
+ this.request = state;
+ this.version = version;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param intentData intent data to copy
+ */
+ public IntentData(IntentData intentData) {
+ checkNotNull(intentData);
+
+ intent = intentData.intent;
+ state = intentData.state;
+ request = intentData.request;
+ version = intentData.version;
+ origin = intentData.origin;
+ installables = intentData.installables;
+ errorCount = intentData.errorCount;
+ }
+
+ // kryo constructor
+ protected IntentData() {
+ intent = null;
+ request = null;
+ }
+
+ /**
+ * Returns the intent this metadata references.
+ *
+ * @return intent
+ */
+ public Intent intent() {
+ return intent;
+ }
+
+ /**
+ * Returns the state of the intent.
+ *
+ * @return intent state
+ */
+ public IntentState state() {
+ return state;
+ }
+
+ public IntentState request() {
+ return request;
+ }
+
+ /**
+ * Returns the intent key.
+ *
+ * @return intent key
+ */
+ public Key key() {
+ return intent.key();
+ }
+
+ /**
+ * Returns the version of the intent for this key.
+ *
+ * @return intent version
+ */
+ public Timestamp version() {
+ return version;
+ }
+
+ /**
+ * Sets the origin, which is the node that created the intent.
+ *
+ * @param origin origin instance
+ */
+ public void setOrigin(NodeId origin) {
+ this.origin = origin;
+ }
+
+ /**
+ * Returns the origin node that created this intent.
+ *
+ * @return origin node ID
+ */
+ public NodeId origin() {
+ return origin;
+ }
+
+ /**
+ * Updates the state of the intent to the given new state.
+ *
+ * @param newState new state of the intent
+ */
+ public void setState(IntentState newState) {
+ this.state = newState;
+ }
+
+ /**
+ * Sets the version for this intent data.
+ * <p>
+ * The store should call this method only once when the IntentData is
+ * first passed into the pending map. Ideally, an IntentData is timestamped
+ * on the same thread that the called used to submit the intents.
+ * </p>
+ *
+ * @param version the version/timestamp for this intent data
+ */
+ public void setVersion(Timestamp version) {
+ this.version = version;
+ }
+
+ /**
+ * Increments the error count for this intent.
+ */
+ public void incrementErrorCount() {
+ errorCount++;
+ }
+
+ /**
+ * Sets the error count for this intent.
+ *
+ * @param newCount new count
+ */
+ public void setErrorCount(int newCount) {
+ errorCount = newCount;
+ }
+
+ /**
+ * Returns the number of times that this intent has encountered an error
+ * during installation or withdrawal.
+ *
+ * @return error count
+ */
+ public int errorCount() {
+ return errorCount;
+ }
+
+ /**
+ * Sets the intent installables to the given list of intents.
+ *
+ * @param installables list of installables for this intent
+ */
+ public void setInstallables(List<Intent> installables) {
+ this.installables = ImmutableList.copyOf(installables);
+ }
+
+ /**
+ * Returns the installables associated with this intent.
+ *
+ * @return list of installable intents
+ */
+ public List<Intent> installables() {
+ return installables != null ? installables : Collections.emptyList();
+ }
+
+ /**
+ * Determines whether an intent data update is allowed. The update must
+ * either have a higher version than the current data, or the state
+ * transition between two updates of the same version must be sane.
+ *
+ * @param currentData existing intent data in the store
+ * @param newData new intent data update proposal
+ * @return true if we can apply the update, otherwise false
+ */
+ public static boolean isUpdateAcceptable(IntentData currentData, IntentData newData) {
+
+ if (currentData == null) {
+ return true;
+ } else if (currentData.version().isOlderThan(newData.version())) {
+ return true;
+ } else if (currentData.version().isNewerThan(newData.version())) {
+ return false;
+ }
+
+ // current and new data versions are the same
+ IntentState currentState = currentData.state();
+ IntentState newState = newData.state();
+
+ switch (newState) {
+ case INSTALLING:
+ if (currentState == INSTALLING) {
+ return false;
+ }
+ // FALLTHROUGH
+ case INSTALLED:
+ if (currentState == INSTALLED) {
+ return false;
+ } else if (currentState == WITHDRAWING || currentState == WITHDRAWN
+ || currentState == PURGE_REQ) {
+ log.warn("Invalid state transition from {} to {} for intent {}",
+ currentState, newState, newData.key());
+ return false;
+ }
+ return true;
+
+ case WITHDRAWING:
+ if (currentState == WITHDRAWING) {
+ return false;
+ }
+ // FALLTHROUGH
+ case WITHDRAWN:
+ if (currentState == WITHDRAWN) {
+ return false;
+ } else if (currentState == INSTALLING || currentState == INSTALLED
+ || currentState == PURGE_REQ) {
+ log.warn("Invalid state transition from {} to {} for intent {}",
+ currentState, newState, newData.key());
+ return false;
+ }
+ return true;
+
+ case FAILED:
+ if (currentState == FAILED) {
+ return false;
+ }
+ return true;
+
+ case CORRUPT:
+ if (currentState == CORRUPT) {
+ return false;
+ }
+ return true;
+
+ case PURGE_REQ:
+ // TODO we should enforce that only WITHDRAWN intents can be purged
+ return true;
+
+ case COMPILING:
+ case RECOMPILING:
+ case INSTALL_REQ:
+ case WITHDRAW_REQ:
+ default:
+ log.warn("Invalid state {} for intent {}", newState, newData.key());
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(intent, version);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final IntentData other = (IntentData) obj;
+ return Objects.equals(this.intent, other.intent)
+ && Objects.equals(this.version, other.version);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("key", key())
+ .add("state", state())
+ .add("version", version())
+ .add("intent", intent())
+ .add("origin", origin())
+ .add("installables", installables())
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentEvent.java
new file mode 100644
index 00000000..b27a5074
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentEvent.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * A class to represent an intent related event.
+ */
+@Beta
+public class IntentEvent extends AbstractEvent<IntentEvent.Type, Intent> {
+
+ public enum Type {
+ /**
+ * Signifies that an intent is to be installed or reinstalled.
+ */
+ INSTALL_REQ,
+
+ /**
+ * Signifies that an intent has been successfully installed.
+ */
+ INSTALLED,
+
+ /**
+ * Signifies that an intent has failed compilation and that it cannot
+ * be satisfied by the network at this time.
+ */
+ FAILED,
+
+ /**
+ * Signifies that an intent will be withdrawn.
+ */
+ WITHDRAW_REQ,
+
+ /**
+ * Signifies that an intent has been withdrawn from the system.
+ */
+ WITHDRAWN,
+
+ /**
+ * Signifies that an intent has failed installation or withdrawal, but
+ * still hold some or all of its resources.
+ * (e.g. link reservations, flow rules on the data plane, etc.)
+ */
+ CORRUPT,
+
+ /**
+ * Signifies that an intent has been purged from the system.
+ */
+ PURGED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified intent and the
+ * current time.
+ *
+ * @param type event type
+ * @param intent subject intent
+ * @param time time the event created in milliseconds since start of epoch
+ */
+ public IntentEvent(Type type, Intent intent, long time) {
+ super(type, intent, time);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified intent and the
+ * current time.
+ *
+ * @param type event type
+ * @param intent subject intent
+ */
+ public IntentEvent(Type type, Intent intent) {
+ super(type, intent);
+ }
+
+ /**
+ * Creates an IntentEvent based on the state contained in the given intent
+ * data. Some states are not sent as external events, and these states will
+ * return null events.
+ *
+ * @param data the intent data to create an event for
+ * @return new intent event if the state is valid, otherwise null.
+ */
+ public static IntentEvent getEvent(IntentData data) {
+ return getEvent(data.state(), data.intent());
+ }
+
+ /**
+ * Creates an IntentEvent based on the given state and intent. Some states
+ * are not sent as external events, and these states will return null events.
+ *
+ * @param state new state of the intent
+ * @param intent intent to put in event
+ * @return new intent event if the state is valid, otherwise null.
+ */
+ public static IntentEvent getEvent(IntentState state, Intent intent) {
+ Type type;
+ switch (state) {
+ case INSTALL_REQ:
+ type = Type.INSTALL_REQ;
+ break;
+ case INSTALLED:
+ type = Type.INSTALLED;
+ break;
+ case WITHDRAW_REQ:
+ type = Type.WITHDRAW_REQ;
+ break;
+ case WITHDRAWN:
+ type = Type.WITHDRAWN;
+ break;
+ case FAILED:
+ type = Type.FAILED;
+ break;
+ case CORRUPT:
+ type = Type.CORRUPT;
+ break;
+ case PURGE_REQ:
+ type = Type.PURGED;
+ break;
+
+ // fallthrough to default from here
+ case COMPILING:
+ case INSTALLING:
+ case RECOMPILING:
+ case WITHDRAWING:
+ default:
+ return null;
+ }
+ return new IntentEvent(type, intent);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentException.java
new file mode 100644
index 00000000..3ac1df50
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentException.java
@@ -0,0 +1,53 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+
+/**
+ * Represents an intent related error.
+ */
+@Beta
+public class IntentException extends RuntimeException {
+
+ private static final long serialVersionUID = 1907263634145241319L;
+
+ /**
+ * Constructs an exception with no message and no underlying cause.
+ */
+ public IntentException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message describing the specific nature of the error
+ */
+ public IntentException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and the underlying cause.
+ *
+ * @param message the message describing the specific nature of the error
+ * @param cause the underlying cause of this error
+ */
+ public IntentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentExtensionService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentExtensionService.java
new file mode 100644
index 00000000..d7c7c641
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentExtensionService.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Map;
+
+/**
+ * Service for extending the capability of intent framework by
+ * adding additional compilers or/and installers.
+ */
+@Beta
+public interface IntentExtensionService {
+ /**
+ * Registers the specified compiler for the given intent class.
+ *
+ * @param cls intent class
+ * @param compiler intent compiler
+ * @param <T> the type of intent
+ */
+ <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler);
+
+ /**
+ * Unregisters the compiler for the specified intent class.
+ *
+ * @param cls intent class
+ * @param <T> the type of intent
+ */
+ <T extends Intent> void unregisterCompiler(Class<T> cls);
+
+ /**
+ * Returns immutable set of bindings of currently registered intent compilers.
+ *
+ * @return the set of compiler bindings
+ */
+ Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentId.java
new file mode 100644
index 00000000..b9a30d2d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentId.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.newresource.ResourceConsumer;
+
+/**
+ * Intent identifier suitable as an external key.
+ * <p>This class is immutable.</p>
+ */
+@Beta
+public final class IntentId implements ResourceConsumer {
+
+ private final long value;
+
+ /**
+ * Creates an intent identifier from the specified long representation.
+ *
+ * @param value long value
+ * @return intent identifier
+ */
+ public static IntentId valueOf(long value) {
+ return new IntentId(value);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ IntentId() {
+ this.value = 0;
+ }
+
+ /**
+ * Constructs the ID corresponding to a given long value.
+ *
+ * @param value the underlying value of this ID
+ */
+ IntentId(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the backing value.
+ *
+ * @return the value
+ */
+ public long fingerprint() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof IntentId)) {
+ return false;
+ }
+ IntentId that = (IntentId) obj;
+ return this.value == that.value;
+ }
+
+ @Override
+ public String toString() {
+ return "0x" + Long.toHexString(value);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentListener.java
new file mode 100644
index 00000000..4858c7ed
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentListener.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onosproject.event.EventListener;
+
+/**
+ * Listener for {@link IntentEvent intent events}.
+ */
+@Beta
+public interface IntentListener extends EventListener<IntentEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentOperation.java
new file mode 100644
index 00000000..1b51b4f3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentOperation.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+
+import com.google.common.annotations.Beta;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of an intent-related operation, e.g. add, remove, replace.
+ */
+@Beta
+public final class IntentOperation {
+
+ private final Type type;
+ private final Intent intent;
+
+ /**
+ * Operation type.
+ */
+ public enum Type {
+ /**
+ * Indicates that an intent should be added.
+ */
+ SUBMIT,
+
+ /**
+ * Indicates that an intent should be removed.
+ */
+ WITHDRAW,
+ }
+
+ /**
+ * Creates an intent operation.
+ *
+ * @param type operation type
+ * @param intent intent subject
+ */
+ public IntentOperation(Type type, Intent intent) {
+ this.type = checkNotNull(type);
+ this.intent = intent;
+ }
+
+ /**
+ * Returns the type of the operation.
+ *
+ * @return operation type
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the identifier of the intent to which this operation applies.
+ *
+ * @return intent identifier
+ */
+ public IntentId intentId() {
+ return intent.id();
+ }
+
+ /**
+ * Returns the key for this intent.
+ *
+ * @return key value
+ */
+ public Key key() {
+ return intent.key();
+ }
+
+ /**
+ * Returns the intent to which this operation applied. For remove,
+ * this can be null.
+ *
+ * @return intent that is the subject of the operation; null for remove
+ */
+ public Intent intent() {
+ return intent;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, intent);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final IntentOperation other = (IntentOperation) obj;
+ return Objects.equals(this.type, other.type) &&
+ Objects.equals(this.intent, other.intent);
+ }
+
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("type", type)
+ .add("intent", intent)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentService.java
new file mode 100644
index 00000000..8533cebc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentService.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+
+import com.google.common.annotations.Beta;
+import org.onosproject.event.ListenerService;
+
+import java.util.List;
+
+/**
+ * Service for application submitting or withdrawing their intents.
+ */
+@Beta
+public interface IntentService
+ extends ListenerService<IntentEvent, IntentListener> {
+
+ /**
+ * Submits an intent into the system.
+ * <p>
+ * This is an asynchronous request meaning that any compiling or
+ * installation activities may be done at later time.
+ * </p>
+ * @param intent intent to be submitted
+ */
+ void submit(Intent intent);
+
+ /**
+ * Withdraws an intent from the system.
+ * <p>
+ * This is an asynchronous request meaning that the environment may be
+ * affected at later time.
+ * </p>
+ * @param intent intent to be withdrawn
+ */
+ void withdraw(Intent intent);
+
+ /**
+ * Purges a specific intent from the system if it is <b>FAILED</b> or
+ * <b>WITHDRAWN</b>. Otherwise, the intent remains in its current state.
+ *
+ * @param intent intent to purge
+ */
+ void purge(Intent intent);
+
+ /**
+ * Fetches an intent based on its key.
+ *
+ * @param key key of the intent
+ * @return intent object if the key is found, null otherwise
+ */
+ Intent getIntent(Key key);
+
+ /**
+ * Returns an iterable of intents currently in the system.
+ *
+ * @return set of intents
+ */
+ Iterable<Intent> getIntents();
+
+ /**
+ * Returns an iterable of intent data objects currently in the system.
+ *
+ * @return set of intent data objects
+ */
+ Iterable<IntentData> getIntentData();
+
+ /**
+ * Returns the number of intents currently in the system.
+ *
+ * @return number of intents
+ */
+ long getIntentCount();
+
+ /**
+ * Retrieves the state of an intent by its identifier.
+ *
+ * @param intentKey intent identifier
+ * @return the intent state or null if one with the given identifier is not
+ * found
+ */
+ IntentState getIntentState(Key intentKey);
+
+ /**
+ * Returns the list of the installable events associated with the specified
+ * top-level intent.
+ *
+ * @param intentKey top-level intent identifier
+ * @return compiled installable intents
+ */
+ List<Intent> getInstallableIntents(Key intentKey);
+
+ /**
+ * Signifies whether the local node is responsible for processing the given
+ * intent key.
+ *
+ * @param intentKey intent key to check
+ * @return true if the local node is responsible for the intent key,
+ * otherwise false
+ */
+ boolean isLocal(Key intentKey);
+
+ /**
+ * Returns the list of intent requests pending processing.
+ *
+ * @return intents pending processing
+ */
+ Iterable<Intent> getPending();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentState.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentState.java
new file mode 100644
index 00000000..1e5fd054
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentState.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Representation of the phases an intent may attain during its lifecycle.
+ */
+@Beta
+public enum IntentState {
+
+ /**
+ * Signifies that the intent has been submitted and will start compiling
+ * shortly. However, this compilation may not necessarily occur on the
+ * local controller instance.
+ * <p>
+ * All intent in the runtime take this state first.
+ * </p><p>
+ * Intents will also pass through this state when they are updated.
+ * </p>
+ */
+ INSTALL_REQ, // TODO submit_REQ?
+
+ /**
+ * Signifies that the intent is being compiled into installable intents.
+ * This is a transitional state after which the intent will enter either
+ * {@link #FAILED} state or {@link #INSTALLING} state.
+ */
+ COMPILING, //TODO do we really need this?
+
+ /**
+ * Signifies that the resulting installable intents are being installed
+ * into the network environment. This is a transitional state after which
+ * the intent will enter either {@link #INSTALLED} state or
+ * {@link #RECOMPILING} state.
+ */
+ INSTALLING,
+
+ /**
+ * The intent has been successfully installed. This is a state where the
+ * intent may remain parked until it is withdrawn by the application or
+ * until the network environment changes in some way to make the original
+ * set of installable intents untenable.
+ */
+ INSTALLED,
+
+ /**
+ * Signifies that the intent is being recompiled into installable intents
+ * as an attempt to adapt to an anomaly in the network environment.
+ * This is a transitional state after which the intent will enter either
+ * {@link #FAILED} state or {@link #INSTALLING} state.
+ * <p>
+ * Exit to the {@link #FAILED} state may be caused by failure to compile
+ * or by compiling into the same set of installable intents which have
+ * previously failed to be installed.
+ * </p>
+ */
+ RECOMPILING, // TODO perhaps repurpose as BROKEN.
+
+ /**
+ * Indicates that an application has requested that an intent be withdrawn.
+ * It will start withdrawing shortly, but not necessarily on this instance.
+ * Intents can also be parked here if it is impossible to withdraw them.
+ */
+ WITHDRAW_REQ,
+
+ /**
+ * Indicates that the intent is being withdrawn. This is a transitional
+ * state, triggered by invocation of the
+ * {@link IntentService#withdraw(Intent)} but one with only one outcome,
+ * which is the the intent being placed in the {@link #WITHDRAWN} state.
+ */
+ WITHDRAWING,
+
+ /**
+ * Indicates that the intent has been successfully withdrawn.
+ */
+ WITHDRAWN,
+
+ /**
+ * Signifies that the intent has failed to be installed and cannot be
+ * satisfied given current network conditions. But, the framework will
+ * reattempt to install it when network conditions change until it is
+ * withdrawn by an application.
+ */
+ FAILED, //TODO consider renaming to UNSATISFIABLE
+
+ /**
+ * Signifies that an intent has failed either installation or withdrawal,
+ * and still hold some or all of its resources.
+ * (e.g. link reservations, flow rules on the data plane, etc.)
+ */
+ CORRUPT, //TODO consider renaming to ERROR
+
+ /**
+ * Indicates that the intent should be purged from the database.
+ * <p>
+ * Note: This operation will only be performed if the intent is already
+ * in WITHDRAWN or FAILED.
+ * </p>
+ */
+ PURGE_REQ
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStore.java
new file mode 100644
index 00000000..167ba152
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStore.java
@@ -0,0 +1,143 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.store.Store;
+
+import java.util.List;
+
+/**
+ * Manages inventory of end-station intents; not intended for direct use.
+ */
+@Beta
+public interface IntentStore extends Store<IntentEvent, IntentStoreDelegate> {
+
+ /**
+ * Returns the number of intents in the store.
+ *
+ * @return the number of intents in the store
+ */
+ long getIntentCount();
+
+ /**
+ * Returns an iterable of all intents in the store.
+ *
+ * @return iterable of all intents
+ */
+ Iterable<Intent> getIntents();
+
+
+ /**
+ * Returns an iterable of all intent data objects in the store.
+ *
+ * @param localOnly should only intents for which this instance is master
+ * be returned
+ * @param olderThan specified duration in milliseconds (0 for "now")
+ * @return iterable of all intent data objects
+ */
+ Iterable<IntentData> getIntentData(boolean localOnly, long olderThan);
+
+ /**
+ * Returns the state of the specified intent.
+ *
+ * @param intentKey intent identification
+ * @return current intent state
+ */
+ IntentState getIntentState(Key intentKey);
+
+ /**
+ * Returns the list of the installable events associated with the specified
+ * original intent.
+ *
+ * @param intentKey original intent identifier
+ * @return compiled installable intents, or null if no installables exist
+ */
+ List<Intent> getInstallableIntents(Key intentKey);
+
+ /**
+ * Writes an IntentData object to the store.
+ *
+ * @param newData new intent data to write
+ */
+ void write(IntentData newData);
+
+ /**
+ * Writes a batch of IntentData objects to the store. A batch has no
+ * semantics, this is simply a convenience API.
+ *
+ * @param updates collection of intent data objects to write
+ */
+ void batchWrite(Iterable<IntentData> updates);
+
+ /**
+ * Returns the intent with the specified identifier.
+ *
+ * @param key key
+ * @return intent or null if not found
+ */
+ Intent getIntent(Key key);
+
+ /**
+ * Returns the intent data object associated with the specified key.
+ *
+ * @param key key to look up
+ * @return intent data object
+ */
+ IntentData getIntentData(Key key);
+
+ /**
+ * Adds a new operation, which should be persisted and delegated.
+ *
+ * @param intent operation
+ */
+ void addPending(IntentData intent);
+
+ /**
+ * Checks to see whether the calling instance is the master for processing
+ * this intent, or more specifically, the key contained in this intent.
+ *
+ * @param intentKey intentKey to check
+ * @return true if master; false, otherwise
+ */
+ //TODO better name
+ boolean isMaster(Key intentKey);
+
+ /**
+ * Returns the intent requests pending processing.
+ *
+ * @return pending intents
+ */
+ Iterable<Intent> getPending();
+
+ /**
+ * Returns the intent data objects that are pending processing.
+ *
+ * @return pending intent data objects
+ */
+ Iterable<IntentData> getPendingData();
+
+ /**
+ * Returns the intent data objects that are pending processing for longer
+ * than the specified duration.
+ *
+ * @param localOnly should only intents for which this instance is master
+ * be returned
+ * @param olderThan specified duration in milliseconds (0 for "now")
+ * @return pending intent data objects
+ */
+ Iterable<IntentData> getPendingData(boolean localOnly, long olderThan);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStoreDelegate.java
new file mode 100644
index 00000000..fd99881c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/IntentStoreDelegate.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Intent store delegate abstraction.
+ */
+@Beta
+public interface IntentStoreDelegate extends StoreDelegate<IntentEvent> {
+
+ /**
+ * Provides an intent data object that should be processed (compiled and
+ * installed) by this manager.
+ *
+ * @param intentData intent data object
+ */
+ void process(IntentData intentData);
+
+ /**
+ * Called when a new intent has been updated for which this node is the master.
+ *
+ * @param intentData intent data object
+ */
+ default void onUpdate(IntentData intentData) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Key.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Key.java
new file mode 100644
index 00000000..18baafc8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/Key.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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import org.onosproject.core.ApplicationId;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * Key class for Intents.
+ */
+// TODO maybe pull this up to utils
+@Beta
+public abstract class Key {
+
+ //TODO consider making this a HashCode object (worry about performance)
+ private final long hash;
+ private static final HashFunction HASH_FN = Hashing.md5();
+
+ protected Key(long hash) {
+ this.hash = hash;
+ }
+
+ public long hash() {
+ return hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(hash);
+ }
+
+ @Override
+ public abstract boolean equals(Object obj);
+
+ /**
+ * Creates a key based on the provided string.
+ * <p>
+ * Note: Two keys with equal value, but different appId, are not equal.
+ * </p>
+ *
+ * @param key the provided string
+ * @param appId application id to associate with this key
+ * @return the key for the string
+ */
+ public static Key of(String key, ApplicationId appId) {
+ return new StringKey(key, appId);
+ }
+
+ /**
+ * Creates a key based on the provided long.
+ * <p>
+ * Note: Two keys with equal value, but different appId, are not equal.
+ * Also, "10" and 10L are different.
+ * </p>
+ *
+ * @param key the provided long
+ * @param appId application id to associate with this key
+ * @return the key for the long
+ */
+ public static Key of(long key, ApplicationId appId) {
+ return new LongKey(key, appId);
+ }
+
+ private static final class StringKey extends Key {
+
+ private final ApplicationId appId;
+ private final String key;
+
+ private StringKey(String key, ApplicationId appId) {
+ super(HASH_FN.newHasher()
+ .putShort(appId.id())
+ .putString(key, StandardCharsets.UTF_8)
+ .hash().asLong());
+ this.key = key;
+ this.appId = appId;
+ }
+
+ @Override
+ public String toString() {
+ return key;
+ }
+
+ // checkstyle requires this
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final StringKey other = (StringKey) obj;
+ return this.hash() == other.hash() &&
+ Objects.equals(this.appId, other.appId) &&
+ Objects.equals(this.key, other.key);
+ }
+ }
+
+ private static final class LongKey extends Key {
+
+ private final ApplicationId appId;
+ private final long key;
+
+ private LongKey(long key, ApplicationId appId) {
+ super(HASH_FN.newHasher()
+ .putShort(appId.id())
+ .putLong(key)
+ .hash().asLong());
+ this.key = key;
+ this.appId = appId;
+ }
+
+ @Override
+ public String toString() {
+ return "0x" + Long.toHexString(key);
+ }
+
+ // checkstyle requires this
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final LongKey other = (LongKey) obj;
+ return this.hash() == other.hash() &&
+ this.key == other.key &&
+ Objects.equals(this.appId, other.appId);
+ }
+ }
+}
+
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java
new file mode 100644
index 00000000..d7953fd8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java
@@ -0,0 +1,241 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Abstraction of a connectivity intent that is implemented by a set of path
+ * segments.
+ */
+@Beta
+public final class LinkCollectionIntent extends ConnectivityIntent {
+
+ private final Set<Link> links;
+
+ private final Set<ConnectPoint> ingressPoints;
+ private final Set<ConnectPoint> egressPoints;
+
+ /**
+ * Creates a new actionable intent capable of funneling the selected
+ * traffic along the specified convergent tree and out the given egress
+ * point satisfying the specified constraints.
+ *
+ * @param appId application identifier
+ * @param key key to use for the intent
+ * @param selector traffic match
+ * @param treatment action
+ * @param links traversed links
+ * @param ingressPoints ingress points
+ * @param egressPoints egress points
+ * @param constraints optional list of constraints
+ * @param priority priority to use for the flows generated by this intent
+ * @throws NullPointerException {@code path} is null
+ */
+ private LinkCollectionIntent(ApplicationId appId,
+ Key key,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ Set<Link> links,
+ Set<ConnectPoint> ingressPoints,
+ Set<ConnectPoint> egressPoints,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, resources(links), selector, treatment, constraints, priority);
+ this.links = links;
+ this.ingressPoints = ingressPoints;
+ this.egressPoints = egressPoints;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected LinkCollectionIntent() {
+ super();
+ this.links = null;
+ this.ingressPoints = null;
+ this.egressPoints = null;
+ }
+
+ /**
+ * Returns a new link collection intent builder. The application id,
+ * ingress point and egress points are required fields. If they are
+ * not set by calls to the appropriate methods, an exception will
+ * be thrown.
+ *
+ * @return single point to multi point builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a single point to multi point intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ Set<Link> links;
+ Set<ConnectPoint> ingressPoints;
+ Set<ConnectPoint> egressPoints;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the ingress point of the single point to multi point intent
+ * that will be built.
+ *
+ * @param ingressPoints ingress connect points
+ * @return this builder
+ */
+ public Builder ingressPoints(Set<ConnectPoint> ingressPoints) {
+ this.ingressPoints = ImmutableSet.copyOf(ingressPoints);
+ return this;
+ }
+
+ /**
+ * Sets the egress points of the single point to multi point intent
+ * that will be built.
+ *
+ * @param egressPoints egress connect points
+ * @return this builder
+ */
+ public Builder egressPoints(Set<ConnectPoint> egressPoints) {
+ this.egressPoints = ImmutableSet.copyOf(egressPoints);
+ return this;
+ }
+
+ /**
+ * Sets the links of the link collection intent
+ * that will be built.
+ *
+ * @param links links for the intent
+ * @return this builder
+ */
+ public Builder links(Set<Link> links) {
+ this.links = ImmutableSet.copyOf(links);
+ return this;
+ }
+
+
+ /**
+ * Builds a single point to multi point intent from the
+ * accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public LinkCollectionIntent build() {
+
+ return new LinkCollectionIntent(
+ appId,
+ key,
+ selector,
+ treatment,
+ links,
+ ingressPoints,
+ egressPoints,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+ /**
+ * Returns the set of links that represent the network connections needed
+ * by this intent.
+ *
+ * @return Set of links for the network hops needed by this intent
+ */
+ public Set<Link> links() {
+ return links;
+ }
+
+ /**
+ * Returns the ingress points of the intent.
+ *
+ * @return the ingress points
+ */
+ public Set<ConnectPoint> ingressPoints() {
+ return ingressPoints;
+ }
+
+ /**
+ * Returns the egress points of the intent.
+ *
+ * @return the egress points
+ */
+ public Set<ConnectPoint> egressPoints() {
+ return egressPoints;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("links", links())
+ .add("ingress", ingressPoints())
+ .add("egress", egressPoints())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsIntent.java
new file mode 100644
index 00000000..bf469dbe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsIntent.java
@@ -0,0 +1,261 @@
+package org.onosproject.net.intent;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+
+/**
+ * Abstraction of MPLS label-switched connectivity.
+ */
+@Beta
+public final class MplsIntent extends ConnectivityIntent {
+
+ private final ConnectPoint ingressPoint;
+ private final Optional<MplsLabel> ingressLabel;
+ private final ConnectPoint egressPoint;
+ private final Optional<MplsLabel> egressLabel;
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports, labels and constraints.
+ *
+ * @param appId application identifier
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param ingressPoint ingress port
+ * @param ingressLabel ingress MPLS label
+ * @param egressPoint egress port
+ * @param egressLabel egress MPLS label
+ * @param constraints optional list of constraints
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if {@code ingressPoint} or {@code egressPoints} is null.
+ */
+ private MplsIntent(ApplicationId appId,
+ Key key,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ ConnectPoint ingressPoint,
+ Optional<MplsLabel> ingressLabel,
+ ConnectPoint egressPoint,
+ Optional<MplsLabel> egressLabel,
+ List<Constraint> constraints,
+ int priority) {
+
+ super(appId, key, Collections.emptyList(), selector, treatment, constraints,
+ priority);
+
+ this.ingressPoint = checkNotNull(ingressPoint);
+ this.ingressLabel = checkNotNull(ingressLabel);
+ this.egressPoint = checkNotNull(egressPoint);
+ this.egressLabel = checkNotNull(egressLabel);
+
+ checkArgument(!ingressPoint.equals(egressPoint),
+ "ingress and egress should be different (ingress: %s, egress: %s)",
+ ingressPoint, egressPoint);
+ }
+
+ /**
+ * Returns a new MPLS intent builder. The application id,
+ * ingress point, egress point, ingress label and egress label are
+ * required fields. If they are not set by calls to the appropriate
+ * methods, an exception will be thrown.
+ *
+ * @return point to point builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of an MPLS intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ ConnectPoint ingressPoint;
+ ConnectPoint egressPoint;
+ Optional<MplsLabel> ingressLabel;
+ Optional<MplsLabel> egressLabel;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the ingress point of the point to point intent that will be built.
+ *
+ * @param ingressPoint ingress connect point
+ * @return this builder
+ */
+ public Builder ingressPoint(ConnectPoint ingressPoint) {
+ this.ingressPoint = ingressPoint;
+ return this;
+ }
+
+ /**
+ * Sets the egress point of the point to point intent that will be built.
+ *
+ * @param egressPoint egress connect point
+ * @return this builder
+ */
+ public Builder egressPoint(ConnectPoint egressPoint) {
+ this.egressPoint = egressPoint;
+ return this;
+ }
+
+ /**
+ * Sets the ingress label of the intent that will be built.
+ *
+ * @param ingressLabel ingress label
+ * @return this builder
+ */
+ public Builder ingressLabel(Optional<MplsLabel> ingressLabel) {
+ this.ingressLabel = ingressLabel;
+ return this;
+ }
+
+ /**
+ * Sets the ingress label of the intent that will be built.
+ *
+ * @param egressLabel ingress label
+ * @return this builder
+ */
+ public Builder egressLabel(Optional<MplsLabel> egressLabel) {
+ this.egressLabel = egressLabel;
+ return this;
+ }
+
+ /**
+ * Builds a point to point intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public MplsIntent build() {
+
+ return new MplsIntent(
+ appId,
+ key,
+ selector,
+ treatment,
+ ingressPoint,
+ ingressLabel,
+ egressPoint,
+ egressLabel,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+
+ /**
+ * Constructor for serializer.
+ */
+ protected MplsIntent() {
+ super();
+ this.ingressPoint = null;
+ this.ingressLabel = null;
+ this.egressPoint = null;
+ this.egressLabel = null;
+ }
+
+ /**
+ * Returns the port on which the ingress traffic should be connected to
+ * the egress.
+ *
+ * @return ingress switch port
+ */
+ public ConnectPoint ingressPoint() {
+ return ingressPoint;
+ }
+
+ /**
+ * Returns the port on which the traffic should egress.
+ *
+ * @return egress switch port
+ */
+ public ConnectPoint egressPoint() {
+ return egressPoint;
+ }
+
+
+ /**
+ * Returns the MPLS label which the ingress traffic should tagged.
+ *
+ * @return ingress MPLS label
+ */
+ public Optional<MplsLabel> ingressLabel() {
+ return ingressLabel;
+ }
+
+ /**
+ * Returns the MPLS label which the egress traffic should tagged.
+ *
+ * @return egress MPLS label
+ */
+ public Optional<MplsLabel> egressLabel() {
+ return egressLabel;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("appId", appId())
+ .add("key", key())
+ .add("priority", priority())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("ingressPoint", ingressPoint)
+ .add("ingressLabel", ingressLabel)
+ .add("egressPoint", egressPoint)
+ .add("egressLabel", egressLabel)
+ .add("constraints", constraints())
+ .toString();
+ }
+
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsPathIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsPathIntent.java
new file mode 100644
index 00000000..3c3c45ce
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MplsPathIntent.java
@@ -0,0 +1,167 @@
+package org.onosproject.net.intent;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+
+/**
+ * Abstraction of explicit MPLS label-switched path.
+ */
+@Beta
+public final class MplsPathIntent extends PathIntent {
+
+ private final Optional<MplsLabel> ingressLabel;
+ private final Optional<MplsLabel> egressLabel;
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports and using the specified explicit path.
+ *
+ * @param appId application identifier
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param path traversed links
+ * @param ingressLabel MPLS egress label
+ * @param egressLabel MPLS ingress label
+ * @param constraints optional list of constraints
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException {@code path} is null
+ */
+ private MplsPathIntent(ApplicationId appId, TrafficSelector selector,
+ TrafficTreatment treatment, Path path, Optional<MplsLabel> ingressLabel,
+ Optional<MplsLabel> egressLabel, List<Constraint> constraints,
+ int priority) {
+ super(appId, selector, treatment, path, constraints,
+ priority);
+
+ this.ingressLabel = checkNotNull(ingressLabel);
+ this.egressLabel = checkNotNull(egressLabel);
+ }
+
+ /**
+ * Returns a new host to host intent builder.
+ *
+ * @return host to host intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a host to host intent.
+ */
+ public static final class Builder extends PathIntent.Builder {
+ private Optional<MplsLabel> ingressLabel = Optional.empty();
+ private Optional<MplsLabel> egressLabel = Optional.empty();
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ @Override
+ public Builder path(Path path) {
+ return (Builder) super.path(path);
+ }
+
+ /**
+ * Sets the ingress label of the intent that will be built.
+ *
+ * @param ingressLabel ingress label
+ * @return this builder
+ */
+ public Builder ingressLabel(Optional<MplsLabel> ingressLabel) {
+ this.ingressLabel = ingressLabel;
+ return this;
+ }
+
+ /**
+ * Sets the ingress label of the intent that will be built.
+ *
+ * @param egressLabel ingress label
+ * @return this builder
+ */
+ public Builder egressLabel(Optional<MplsLabel> egressLabel) {
+ this.egressLabel = egressLabel;
+ return this;
+ }
+
+
+ /**
+ * Builds a host to host intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public MplsPathIntent build() {
+
+ return new MplsPathIntent(
+ appId,
+ selector,
+ treatment,
+ path,
+ ingressLabel,
+ egressLabel,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+ /**
+ * Returns the MPLS label which the ingress traffic should tagged.
+ *
+ * @return ingress MPLS label
+ */
+ public Optional<MplsLabel> ingressLabel() {
+ return ingressLabel;
+ }
+
+ /**
+ * Returns the MPLS label which the egress traffic should tagged.
+ *
+ * @return egress MPLS label
+ */
+ public Optional<MplsLabel> egressLabel() {
+ return egressLabel;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java
new file mode 100644
index 00000000..ac6061c7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java
@@ -0,0 +1,223 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of multiple source to single destination connectivity intent.
+ */
+@Beta
+public final class MultiPointToSinglePointIntent extends ConnectivityIntent {
+
+ private final Set<ConnectPoint> ingressPoints;
+ private final ConnectPoint egressPoint;
+
+ /**
+ * Creates a new multi-to-single point connectivity intent for the specified
+ * traffic selector and treatment.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param ingressPoints set of ports from which ingress traffic originates
+ * @param egressPoint port to which traffic will egress
+ * @param constraints constraints to apply to the intent
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if {@code ingressPoints} or
+ * {@code egressPoint} is null.
+ * @throws IllegalArgumentException if the size of {@code ingressPoints} is
+ * not more than 1
+ */
+ private MultiPointToSinglePointIntent(ApplicationId appId,
+ Key key,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ Set<ConnectPoint> ingressPoints,
+ ConnectPoint egressPoint,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, Collections.emptyList(), selector, treatment, constraints,
+ priority);
+
+ checkNotNull(ingressPoints);
+ checkArgument(!ingressPoints.isEmpty(), "Ingress point set cannot be empty");
+ checkNotNull(egressPoint);
+ checkArgument(!ingressPoints.contains(egressPoint),
+ "Set of ingresses should not contain egress (egress: %s)", egressPoint);
+
+ this.ingressPoints = Sets.newHashSet(ingressPoints);
+ this.egressPoint = egressPoint;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected MultiPointToSinglePointIntent() {
+ super();
+ this.ingressPoints = null;
+ this.egressPoint = null;
+ }
+
+ /**
+ * Returns a new multi point to single point intent builder. The application id,
+ * ingress points and egress point are required fields. If they are
+ * not set by calls to the appropriate methods, an exception will
+ * be thrown.
+ *
+ * @return single point to multi point builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a multi point to single point intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ Set<ConnectPoint> ingressPoints;
+ ConnectPoint egressPoint;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the ingress point of the single point to multi point intent
+ * that will be built.
+ *
+ * @param ingressPoints ingress connect points
+ * @return this builder
+ */
+ public Builder ingressPoints(Set<ConnectPoint> ingressPoints) {
+ this.ingressPoints = ImmutableSet.copyOf(ingressPoints);
+ return this;
+ }
+
+ /**
+ * Sets the egress point of the multi point to single point intent
+ * that will be built.
+ *
+ * @param egressPoint egress connect point
+ * @return this builder
+ */
+ public Builder egressPoint(ConnectPoint egressPoint) {
+ this.egressPoint = egressPoint;
+ return this;
+ }
+
+ /**
+ * Builds a multi point to single point intent from the
+ * accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public MultiPointToSinglePointIntent build() {
+
+ return new MultiPointToSinglePointIntent(
+ appId,
+ key,
+ selector,
+ treatment,
+ ingressPoints,
+ egressPoint,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+ /**
+ * Returns the set of ports on which ingress traffic should be connected to
+ * the egress port.
+ *
+ * @return set of ingress ports
+ */
+ public Set<ConnectPoint> ingressPoints() {
+ return ingressPoints;
+ }
+
+ /**
+ * Returns the port on which the traffic should egress.
+ *
+ * @return egress port
+ */
+ public ConnectPoint egressPoint() {
+ return egressPoint;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("ingress", ingressPoints())
+ .add("egress", egressPoint())
+ .add("constraints", constraints())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalCircuitIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalCircuitIntent.java
new file mode 100644
index 00000000..1e515c8c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalCircuitIntent.java
@@ -0,0 +1,219 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.OduCltPort;
+
+import java.util.Collections;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An optical layer intent for circuits between two OduClt ports.
+ * No traffic selector or traffic treatment are needed.
+ */
+@Beta
+public class OpticalCircuitIntent extends Intent {
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+ private final OduCltPort.SignalType signalType;
+ private final boolean isBidirectional;
+
+ /**
+ * Creates an optical circuit intent between the specified
+ * connection points.
+ *
+ * @param appId application identification
+ * @param key intent key
+ * @param src the source transponder port
+ * @param dst the destination transponder port
+ * @param signalType ODU signal type
+ * @param isBidirectional indicate if intent is bidirectional
+ * @param priority priority to use for flows from this intent
+ */
+ protected OpticalCircuitIntent(ApplicationId appId, Key key, ConnectPoint src, ConnectPoint dst,
+ OduCltPort.SignalType signalType, boolean isBidirectional, int priority) {
+ super(appId, key, Collections.emptyList(), priority);
+ this.src = checkNotNull(src);
+ this.dst = checkNotNull(dst);
+ this.signalType = checkNotNull(signalType);
+ this.isBidirectional = isBidirectional;
+ }
+
+ /**
+ * Returns a new optical circuit intent builder.
+ *
+ * @return host to host intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+
+ /**
+ * Builder for optical circuit intents.
+ */
+ public static class Builder extends Intent.Builder {
+ private ConnectPoint src;
+ private ConnectPoint dst;
+ private OduCltPort.SignalType signalType;
+ private boolean isBidirectional;
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the source for the intent that will be built.
+ *
+ * @param src source to use for built intent
+ * @return this builder
+ */
+ public Builder src(ConnectPoint src) {
+ this.src = src;
+ return this;
+ }
+
+ /**
+ * Sets the destination for the intent that will be built.
+ *
+ * @param dst dest to use for built intent
+ * @return this builder
+ */
+ public Builder dst(ConnectPoint dst) {
+ this.dst = dst;
+ return this;
+ }
+
+ /**
+ * Sets the ODU signal type for the intent that will be built.
+ *
+ * @param signalType signal type to use for built intent
+ * @return this builder
+ */
+ public Builder signalType(OduCltPort.SignalType signalType) {
+ this.signalType = signalType;
+ return this;
+ }
+
+ /**
+ * Sets the directionality of the intent.
+ *
+ * @param isBidirectional true if bidirectional, false if unidirectional
+ * @return this builder
+ */
+ public Builder bidirectional(boolean isBidirectional) {
+ this.isBidirectional = isBidirectional;
+ return this;
+ }
+
+ /**
+ * Builds an optical circuit intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public OpticalCircuitIntent build() {
+
+ return new OpticalCircuitIntent(
+ appId,
+ key,
+ src,
+ dst,
+ signalType,
+ isBidirectional,
+ priority
+ );
+ }
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected OpticalCircuitIntent() {
+ super();
+ this.src = null;
+ this.dst = null;
+ this.signalType = null;
+ this.isBidirectional = false;
+ }
+
+ /**
+ * Returns the source transponder port.
+ *
+ * @return source transponder port
+ */
+ public ConnectPoint getSrc() {
+ return src;
+ }
+
+ /**
+ * Returns the destination transponder port.
+ *
+ * @return source transponder port
+ */
+ public ConnectPoint getDst() {
+ return dst;
+ }
+
+ /**
+ * Returns the ODU signal type.
+ *
+ * @return ODU signal type
+ */
+ public OduCltPort.SignalType getSignalType() {
+ return signalType;
+ }
+
+ /**
+ * Returns the directionality of the intent.
+ *
+ * @return true if bidirectional, false if unidirectional
+ */
+ public boolean isBidirectional() {
+ return isBidirectional;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("src", src)
+ .add("dst", dst)
+ .add("signalType", signalType)
+ .add("isBidirectional", isBidirectional)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalConnectivityIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalConnectivityIntent.java
new file mode 100644
index 00000000..aeb0255f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalConnectivityIntent.java
@@ -0,0 +1,223 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.OduSignalType;
+
+import java.util.Collections;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An optical layer intent for connectivity between two OCh ports.
+ * No traffic selector or traffic treatment are needed.
+ */
+@Beta
+public final class OpticalConnectivityIntent extends Intent {
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+ private final OduSignalType signalType;
+ private final boolean isBidirectional;
+
+ /**
+ * Creates an optical connectivity intent between the specified
+ * connection points.
+ *
+ * @param appId application identification
+ * @param key intent key
+ * @param src the source transponder port
+ * @param dst the destination transponder port
+ * @param signalType signal type
+ * @param isBidirectional indicates if intent is unidirectional
+ * @param priority priority to use for flows from this intent
+ */
+ protected OpticalConnectivityIntent(ApplicationId appId,
+ Key key,
+ ConnectPoint src,
+ ConnectPoint dst,
+ OduSignalType signalType,
+ boolean isBidirectional,
+ int priority) {
+ super(appId, key, Collections.emptyList(), priority);
+ this.src = checkNotNull(src);
+ this.dst = checkNotNull(dst);
+ this.signalType = checkNotNull(signalType);
+ this.isBidirectional = isBidirectional;
+ }
+
+ /**
+ * Returns a new optical connectivity intent builder.
+ *
+ * @return host to host intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+
+ /**
+ * Builder for optical connectivity intents.
+ */
+ public static class Builder extends Intent.Builder {
+ private ConnectPoint src;
+ private ConnectPoint dst;
+ private OduSignalType signalType;
+ private boolean isBidirectional;
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the source for the intent that will be built.
+ *
+ * @param src source to use for built intent
+ * @return this builder
+ */
+ public Builder src(ConnectPoint src) {
+ this.src = src;
+ return this;
+ }
+
+ /**
+ * Sets the destination for the intent that will be built.
+ *
+ * @param dst dest to use for built intent
+ * @return this builder
+ */
+ public Builder dst(ConnectPoint dst) {
+ this.dst = dst;
+ return this;
+ }
+
+ /**
+ * Sets the ODU signal type for the intent that will be built.
+ *
+ * @param signalType ODU signal type
+ * @return this builder
+ */
+ public Builder signalType(OduSignalType signalType) {
+ this.signalType = signalType;
+ return this;
+ }
+
+ /**
+ * Sets the directionality of the intent.
+ *
+ * @param isBidirectional true if bidirectional, false if unidirectional
+ * @return this builder
+ */
+ public Builder bidirectional(boolean isBidirectional) {
+ this.isBidirectional = isBidirectional;
+ return this;
+ }
+
+ /**
+ * Builds an optical connectivity intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public OpticalConnectivityIntent build() {
+
+ return new OpticalConnectivityIntent(
+ appId,
+ key,
+ src,
+ dst,
+ signalType,
+ isBidirectional,
+ priority
+ );
+ }
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected OpticalConnectivityIntent() {
+ super();
+ this.src = null;
+ this.dst = null;
+ this.signalType = null;
+ this.isBidirectional = false;
+ }
+
+ /**
+ * Returns the source transponder port.
+ *
+ * @return source transponder port
+ */
+ public ConnectPoint getSrc() {
+ return src;
+ }
+
+ /**
+ * Returns the destination transponder port.
+ *
+ * @return source transponder port
+ */
+ public ConnectPoint getDst() {
+ return dst;
+ }
+
+ /**
+ * Returns the ODU signal type.
+ *
+ * @return ODU signal type
+ */
+ public OduSignalType getSignalType() {
+ return signalType;
+ }
+
+ /**
+ * Returns the directionality of the intent.
+ *
+ * @return true if bidirectional, false if unidirectional
+ */
+ public boolean isBidirectional() {
+ return isBidirectional;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("src", src)
+ .add("dst", dst)
+ .add("signalType", signalType)
+ .add("isBidirectional", isBidirectional)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalPathIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalPathIntent.java
new file mode 100644
index 00000000..5a5461cb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/OpticalPathIntent.java
@@ -0,0 +1,234 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.Path;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An optical layer intent with explicitly selected path.
+ */
+@Beta
+public final class OpticalPathIntent extends Intent {
+
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+ private final Path path;
+ private final OchSignal lambda;
+ private final OchSignalType signalType;
+ private final boolean isBidirectional;
+
+ private OpticalPathIntent(ApplicationId appId,
+ Key key,
+ ConnectPoint src,
+ ConnectPoint dst,
+ Path path,
+ OchSignal lambda,
+ OchSignalType signalType,
+ boolean isBidirectional,
+ int priority) {
+ super(appId, key, ImmutableSet.copyOf(path.links()), priority);
+ this.src = checkNotNull(src);
+ this.dst = checkNotNull(dst);
+ this.path = checkNotNull(path);
+ this.lambda = checkNotNull(lambda);
+ this.signalType = checkNotNull(signalType);
+ this.isBidirectional = isBidirectional;
+ }
+
+ protected OpticalPathIntent() {
+ this.src = null;
+ this.dst = null;
+ this.path = null;
+ this.lambda = null;
+ this.signalType = null;
+ this.isBidirectional = true;
+ }
+
+ /**
+ * Returns a new optical connectivity intent builder.
+ *
+ * @return host to host intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+
+ /**
+ * Builder for optical path intents.
+ */
+ public static class Builder extends Intent.Builder {
+ private ConnectPoint src;
+ private ConnectPoint dst;
+ private Path path;
+ private OchSignal lambda;
+ private OchSignalType signalType;
+ private boolean isBidirectional;
+ Key key;
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the source for the intent that will be built.
+ *
+ * @param src source to use for built intent
+ * @return this builder
+ */
+ public Builder src(ConnectPoint src) {
+ this.src = src;
+ return this;
+ }
+
+ /**
+ * Sets the destination for the intent that will be built.
+ *
+ * @param dst dest to use for built intent
+ * @return this builder
+ */
+ public Builder dst(ConnectPoint dst) {
+ this.dst = dst;
+ return this;
+ }
+
+ /**
+ * Sets the path for the intent that will be built.
+ *
+ * @param path path to use for built intent
+ * @return this builder
+ */
+ public Builder path(Path path) {
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Sets the optical channel (lambda) for the intent that will be built.
+ *
+ * @param lambda the optical channel
+ * @return this builder
+ */
+ public Builder lambda(OchSignal lambda) {
+ this.lambda = lambda;
+ return this;
+ }
+
+ /**
+ * Sets the optical signal type for the intent that will be built.
+ *
+ * @param signalType the optical signal type
+ * @return this builder
+ */
+ public Builder signalType(OchSignalType signalType) {
+ this.signalType = signalType;
+ return this;
+ }
+
+ /**
+ * Sets the intent's direction.
+ *
+ * @param isBidirectional indicates if intent is bidirectional
+ * @return this builder
+ */
+ public Builder bidirectional(boolean isBidirectional) {
+ this.isBidirectional = isBidirectional;
+ return this;
+ }
+
+ /**
+ * Builds an optical path intent from the accumulated parameters.
+ *
+ * @return optical path intent
+ */
+ public OpticalPathIntent build() {
+
+ return new OpticalPathIntent(
+ appId,
+ key,
+ src,
+ dst,
+ path,
+ lambda,
+ signalType,
+ isBidirectional,
+ priority
+ );
+ }
+ }
+
+
+ public ConnectPoint src() {
+ return src;
+ }
+
+ public ConnectPoint dst() {
+ return dst;
+ }
+
+ public Path path() {
+ return path;
+ }
+
+ public OchSignal lambda() {
+ return lambda;
+ }
+
+ public OchSignalType signalType() {
+ return signalType;
+ }
+
+ public boolean isBidirectional() {
+ return isBidirectional;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("appId", appId())
+ .add("key", key())
+ .add("resources", resources())
+ .add("ingressPort", src)
+ .add("egressPort", dst)
+ .add("path", path)
+ .add("lambda", lambda)
+ .add("signalType", signalType)
+ .add("isBidirectional", isBidirectional)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEvent.java
new file mode 100644
index 00000000..c79a3818
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEvent.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Partition event.
+ */
+//TODO change String into a proper object type
+@Beta
+public class PartitionEvent extends AbstractEvent<PartitionEvent.Type, String> {
+
+ public enum Type {
+ LEADER_CHANGED
+ }
+
+ public PartitionEvent(Type type, String partition) {
+ super(type, partition);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEventListener.java
new file mode 100644
index 00000000..5f1da334
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionEventListener.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving device partition-related events.
+ */
+@Beta
+public interface PartitionEventListener extends EventListener<PartitionEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionService.java
new file mode 100644
index 00000000..02ccccac
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PartitionService.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.event.ListenerService;
+
+/**
+ * Service for interacting with the partition-to-instance assignments.
+ */
+@Beta
+public interface PartitionService
+ extends ListenerService<PartitionEvent, PartitionEventListener> {
+
+ /**
+ * Returns whether the given intent key is in a partition owned by this
+ * instance or not.
+ *
+ * @param intentKey intent key to query
+ * @return true if the key is owned by this instance, otherwise false
+ */
+ boolean isMine(Key intentKey);
+
+ /**
+ * Returns the leader for a particular key.
+ *
+ * @param intentKey intent key to query
+ * @return the leader node
+ */
+ NodeId getLeader(Key intentKey);
+
+ // TODO add API for rebalancing partitions
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
new file mode 100644
index 00000000..dffbabfe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
@@ -0,0 +1,202 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Abstraction of explicitly path specified connectivity intent.
+ */
+@Beta
+public class PathIntent extends ConnectivityIntent {
+
+ private final Path path;
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports and using the specified explicit path.
+ *
+ * @param appId application identifier
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param path traversed links
+ * @param constraints optional list of constraints
+ * @param priority priority to use for the generated flows
+ * @throws NullPointerException {@code path} is null
+ */
+ protected PathIntent(ApplicationId appId,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ Path path,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, null, resources(path.links()), selector, treatment, constraints,
+ priority);
+ PathIntent.validate(path.links());
+ this.path = path;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected PathIntent() {
+ super();
+ this.path = null;
+ }
+
+ /**
+ * Returns a new host to host intent builder.
+ *
+ * @return host to host intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a host to host intent.
+ */
+ public static class Builder extends ConnectivityIntent.Builder {
+ Path path;
+
+ protected Builder() {
+ // Hide default constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the path of the intent that will be built.
+ *
+ * @param path path for the intent
+ * @return this builder
+ */
+ public Builder path(Path path) {
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Builds a path intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public PathIntent build() {
+
+ return new PathIntent(
+ appId,
+ selector,
+ treatment,
+ path,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+
+ // NOTE: This methods takes linear time with the number of links.
+ /**
+ * Validates that source element ID and destination element ID of a link are
+ * different for the specified all links and that destination element ID of a link and source
+ * element ID of the next adjacent source element ID are same for the specified all links.
+ *
+ * @param links links to be validated
+ */
+ public static void validate(List<Link> links) {
+ checkArgument(Iterables.all(links, new Predicate<Link>() {
+ @Override
+ public boolean apply(Link link) {
+ return !link.src().elementId().equals(link.dst().elementId());
+ }
+ }), "element of src and dst in a link must be different: {}", links);
+
+ boolean adjacentSame = true;
+ for (int i = 0; i < links.size() - 1; i++) {
+ if (!links.get(i).dst().elementId().equals(links.get(i + 1).src().elementId())) {
+ adjacentSame = false;
+ break;
+ }
+ }
+ checkArgument(adjacentSame, "adjacent links must share the same element: {}", links);
+ }
+
+ /**
+ * Returns the links which the traffic goes along.
+ *
+ * @return traversed links
+ */
+ public Path path() {
+ return path;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("constraints", constraints())
+ .add("path", path)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java
new file mode 100644
index 00000000..d3f7529d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.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.intent;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of point-to-point connectivity.
+ */
+@Beta
+public final class PointToPointIntent extends ConnectivityIntent {
+
+ private final ConnectPoint ingressPoint;
+ private final ConnectPoint egressPoint;
+
+ /**
+ * Returns a new point to point intent builder. The application id,
+ * ingress point and egress point are required fields. If they are
+ * not set by calls to the appropriate methods, an exception will
+ * be thrown.
+ *
+ * @return point to point builder
+ */
+ public static PointToPointIntent.Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a point to point intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ ConnectPoint ingressPoint;
+ ConnectPoint egressPoint;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the ingress point of the point to point intent that will be built.
+ *
+ * @param ingressPoint ingress connect point
+ * @return this builder
+ */
+ public Builder ingressPoint(ConnectPoint ingressPoint) {
+ this.ingressPoint = ingressPoint;
+ return this;
+ }
+
+ /**
+ * Sets the egress point of the point to point intent that will be built.
+ *
+ * @param egressPoint egress connect point
+ * @return this builder
+ */
+ public Builder egressPoint(ConnectPoint egressPoint) {
+ this.egressPoint = egressPoint;
+ return this;
+ }
+
+ /**
+ * Builds a point to point intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public PointToPointIntent build() {
+
+ return new PointToPointIntent(
+ appId,
+ key,
+ selector,
+ treatment,
+ ingressPoint,
+ egressPoint,
+ constraints,
+ priority
+ );
+ }
+ }
+
+
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports and constraints.
+ *
+ * @param appId application identifier
+ * @param key key of the intent
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param ingressPoint ingress port
+ * @param egressPoint egress port
+ * @param constraints optional list of constraints
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if {@code ingressPoint} or
+ * {@code egressPoints} or {@code appId} is null.
+ */
+ private PointToPointIntent(ApplicationId appId,
+ Key key,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, Collections.emptyList(), selector, treatment, constraints,
+ priority);
+
+ checkArgument(!ingressPoint.equals(egressPoint),
+ "ingress and egress should be different (ingress: %s, egress: %s)", ingressPoint, egressPoint);
+
+ this.ingressPoint = checkNotNull(ingressPoint);
+ this.egressPoint = checkNotNull(egressPoint);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected PointToPointIntent() {
+ super();
+ this.ingressPoint = null;
+ this.egressPoint = null;
+ }
+
+ /**
+ * Returns the port on which the ingress traffic should be connected to
+ * the egress.
+ *
+ * @return ingress port
+ */
+ public ConnectPoint ingressPoint() {
+ return ingressPoint;
+ }
+
+ /**
+ * Returns the port on which the traffic should egress.
+ *
+ * @return egress port
+ */
+ public ConnectPoint egressPoint() {
+ return egressPoint;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("ingress", ingressPoint)
+ .add("egress", egressPoint)
+ .add("constraints", constraints())
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java
new file mode 100644
index 00000000..de555cf0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java
@@ -0,0 +1,219 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of single source, multiple destination connectivity intent.
+ */
+@Beta
+public final class SinglePointToMultiPointIntent extends ConnectivityIntent {
+
+ private final ConnectPoint ingressPoint;
+ private final Set<ConnectPoint> egressPoints;
+
+ /**
+ * Creates a new single-to-multi point connectivity intent.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param ingressPoint port on which traffic will ingress
+ * @param egressPoints set of ports on which traffic will egress
+ * @param constraints constraints to apply to the intent
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if {@code ingressPoint} or
+ * {@code egressPoints} is null
+ * @throws IllegalArgumentException if the size of {@code egressPoints} is
+ * not more than 1
+ */
+ private SinglePointToMultiPointIntent(ApplicationId appId,
+ Key key,
+ TrafficSelector selector, TrafficTreatment treatment,
+ ConnectPoint ingressPoint, Set<ConnectPoint> egressPoints,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, Collections.emptyList(), selector, treatment, constraints,
+ priority);
+ checkNotNull(egressPoints);
+ checkNotNull(ingressPoint);
+ checkArgument(!egressPoints.isEmpty(), "Egress point set cannot be empty");
+ checkArgument(!egressPoints.contains(ingressPoint),
+ "Set of egresses should not contain ingress (ingress: %s)", ingressPoint);
+
+ this.ingressPoint = checkNotNull(ingressPoint);
+ this.egressPoints = egressPoints;
+ }
+
+ /**
+ * Returns a new single point to multi point intent builder. The application id,
+ * ingress point and egress points are required fields. If they are
+ * not set by calls to the appropriate methods, an exception will
+ * be thrown.
+ *
+ * @return single point to multi point builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a single point to multi point intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ ConnectPoint ingressPoint;
+ Set<ConnectPoint> egressPoints;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the ingress point of the single point to multi point intent
+ * that will be built.
+ *
+ * @param ingressPoint ingress connect point
+ * @return this builder
+ */
+ public Builder ingressPoint(ConnectPoint ingressPoint) {
+ this.ingressPoint = ingressPoint;
+ return this;
+ }
+
+ /**
+ * Sets the egress points of the single point to multi point intent
+ * that will be built.
+ *
+ * @param egressPoints egress connect points
+ * @return this builder
+ */
+ public Builder egressPoints(Set<ConnectPoint> egressPoints) {
+ this.egressPoints = ImmutableSet.copyOf(egressPoints);
+ return this;
+ }
+
+ /**
+ * Builds a single point to multi point intent from the
+ * accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public SinglePointToMultiPointIntent build() {
+
+ return new SinglePointToMultiPointIntent(
+ appId,
+ key,
+ selector,
+ treatment,
+ ingressPoint,
+ egressPoints,
+ constraints,
+ priority
+ );
+ }
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected SinglePointToMultiPointIntent() {
+ super();
+ this.ingressPoint = null;
+ this.egressPoints = null;
+ }
+
+ /**
+ * Returns the port on which the ingress traffic should be connected to the
+ * egress.
+ *
+ * @return ingress port
+ */
+ public ConnectPoint ingressPoint() {
+ return ingressPoint;
+ }
+
+ /**
+ * Returns the set of ports on which the traffic should egress.
+ *
+ * @return set of egress ports
+ */
+ public Set<ConnectPoint> egressPoints() {
+ return egressPoints;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("ingress", ingressPoint)
+ .add("egress", egressPoints)
+ .add("constraints", constraints())
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/TwoWayP2PIntent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/TwoWayP2PIntent.java
new file mode 100644
index 00000000..b9f126f4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/TwoWayP2PIntent.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;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of bidirectional connectivity between two points in the network.
+ */
+@Beta
+public final class TwoWayP2PIntent extends ConnectivityIntent {
+
+ private final ConnectPoint one;
+ private final ConnectPoint two;
+
+
+ /**
+ * Creates a new host-to-host intent with the supplied host pair.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ * @param one first host
+ * @param two second host
+ * @param selector action
+ * @param treatment ingress port
+ * @param constraints optional prioritized list of path selection constraints
+ * @param priority priority to use for flows generated by this intent
+ * @throws NullPointerException if {@code one} or {@code two} is null.
+ */
+ private TwoWayP2PIntent(ApplicationId appId, Key key,
+ ConnectPoint one, ConnectPoint two,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ List<Constraint> constraints,
+ int priority) {
+ super(appId, key, Collections.emptyList(), selector, treatment, constraints,
+ priority);
+
+ // TODO: consider whether the case one and two are same is allowed
+ this.one = checkNotNull(one);
+ this.two = checkNotNull(two);
+
+ }
+
+ /**
+ * Returns a new two way intent builder.
+ *
+ * @return two way intent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of a point to point intent.
+ */
+ public static final class Builder extends ConnectivityIntent.Builder {
+ ConnectPoint one;
+ ConnectPoint two;
+
+ private Builder() {
+ // Hide constructor
+ }
+
+ @Override
+ public Builder appId(ApplicationId appId) {
+ return (Builder) super.appId(appId);
+ }
+
+ @Override
+ public Builder key(Key key) {
+ return (Builder) super.key(key);
+ }
+
+ @Override
+ public Builder selector(TrafficSelector selector) {
+ return (Builder) super.selector(selector);
+ }
+
+ @Override
+ public Builder treatment(TrafficTreatment treatment) {
+ return (Builder) super.treatment(treatment);
+ }
+
+ @Override
+ public Builder constraints(List<Constraint> constraints) {
+ return (Builder) super.constraints(constraints);
+ }
+
+ @Override
+ public Builder priority(int priority) {
+ return (Builder) super.priority(priority);
+ }
+
+ /**
+ * Sets the first connection point of the two way intent that will be built.
+ *
+ * @param one first connect point
+ * @return this builder
+ */
+ public Builder one(ConnectPoint one) {
+ this.one = one;
+ return this;
+ }
+
+ /**
+ * Sets the second connection point of the two way intent that will be built.
+ *
+ * @param two second connect point
+ * @return this builder
+ */
+ public Builder two(ConnectPoint two) {
+ this.two = two;
+ return this;
+ }
+
+ /**
+ * Builds a point to point intent from the accumulated parameters.
+ *
+ * @return point to point intent
+ */
+ public TwoWayP2PIntent build() {
+
+ return new TwoWayP2PIntent(
+ appId,
+ key,
+ one,
+ two,
+ selector,
+ treatment,
+ constraints,
+ priority
+ );
+ }
+ }
+
+ /**
+ * Returns identifier of the first host.
+ *
+ * @return first host identifier
+ */
+ public ConnectPoint one() {
+ return one;
+ }
+
+ /**
+ * Returns identifier of the second host.
+ *
+ * @return second host identifier
+ */
+ public ConnectPoint two() {
+ return two;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("key", key())
+ .add("appId", appId())
+ .add("priority", priority())
+ .add("resources", resources())
+ .add("selector", selector())
+ .add("treatment", treatment())
+ .add("constraints", constraints())
+ .add("one", one)
+ .add("two", two)
+ .toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AnnotationConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AnnotationConstraint.java
new file mode 100644
index 00000000..f5439efd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AnnotationConstraint.java
@@ -0,0 +1,113 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.Link;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Objects;
+
+import static org.onosproject.net.AnnotationKeys.getAnnotatedValue;
+
+/**
+ * Constraint that evaluates an arbitrary link annotated value is under the specified threshold.
+ */
+@Beta
+public class AnnotationConstraint extends BooleanConstraint {
+
+ private final String key;
+ private final double threshold;
+
+ /**
+ * Creates a new constraint to keep the value for the specified key
+ * of link annotation under the threshold.
+ *
+ * @param key key of link annotation
+ * @param threshold threshold value of the specified link annotation
+ */
+ public AnnotationConstraint(String key, double threshold) {
+ this.key = key;
+ this.threshold = threshold;
+ }
+
+ // Constructor for serialization
+ private AnnotationConstraint() {
+ this.key = "";
+ this.threshold = 0;
+ }
+
+ /**
+ * Returns the key of link annotation this constraint designates.
+ * @return key of link annotation
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns the threshold this constraint ensures as link annotated value.
+ *
+ * @return threshold as link annotated value
+ */
+ public double threshold() {
+ return threshold;
+ }
+
+ @Override
+ public boolean isValid(Link link, LinkResourceService resourceService) {
+ double value = getAnnotatedValue(link, key);
+
+ return value <= threshold;
+ }
+
+ @Override
+ public double cost(Link link, LinkResourceService resourceService) {
+ if (isValid(link, resourceService)) {
+ return getAnnotatedValue(link, key);
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, threshold);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof AnnotationConstraint)) {
+ return false;
+ }
+
+ final AnnotationConstraint other = (AnnotationConstraint) obj;
+ return Objects.equals(this.key, other.key) && Objects.equals(this.threshold, other.threshold);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("key", key)
+ .add("threshold", threshold)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AsymmetricPathConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AsymmetricPathConstraint.java
new file mode 100644
index 00000000..e0f8614c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/AsymmetricPathConstraint.java
@@ -0,0 +1,64 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Constraint that serves as a request for asymmetric bi-directional path.
+ */
+@Beta
+public class AsymmetricPathConstraint implements Constraint {
+
+ @Override
+ public double cost(Link link, LinkResourceService resourceService) {
+ return 1;
+ }
+
+ @Override
+ public boolean validate(Path path, LinkResourceService resourceService) {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(true);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BandwidthConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BandwidthConstraint.java
new file mode 100644
index 00000000..43b8e4b1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BandwidthConstraint.java
@@ -0,0 +1,96 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.BandwidthResourceRequest;
+import org.onosproject.net.resource.link.LinkResourceService;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Constraint that evaluates links based on available bandwidths.
+ */
+@Beta
+public class BandwidthConstraint extends BooleanConstraint {
+
+ private final BandwidthResource bandwidth;
+
+ /**
+ * Creates a new bandwidth constraint.
+ *
+ * @param bandwidth required bandwidth
+ */
+ public BandwidthConstraint(BandwidthResource bandwidth) {
+ this.bandwidth = checkNotNull(bandwidth, "Bandwidth cannot be null");
+ }
+
+ // Constructor for serialization
+ private BandwidthConstraint() {
+ this.bandwidth = null;
+ }
+
+ @Override
+ public boolean isValid(Link link, LinkResourceService resourceService) {
+ for (ResourceRequest request : resourceService.getAvailableResources(link)) {
+ if (request.type() == ResourceType.BANDWIDTH) {
+ BandwidthResourceRequest brr = (BandwidthResourceRequest) request;
+ if (brr.bandwidth().toDouble() >= bandwidth.toDouble()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the bandwidth required by this constraint.
+ *
+ * @return required bandwidth
+ */
+ public BandwidthResource bandwidth() {
+ return bandwidth;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(bandwidth);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final BandwidthConstraint other = (BandwidthConstraint) obj;
+ return Objects.equals(this.bandwidth, other.bandwidth);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("bandwidth", bandwidth).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BooleanConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BooleanConstraint.java
new file mode 100644
index 00000000..f1d4ad9f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/BooleanConstraint.java
@@ -0,0 +1,64 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+/**
+ * Abstract base class for various constraints that evaluate link viability
+ * in a yes/no fashion.
+ */
+@Beta
+public abstract class BooleanConstraint implements Constraint {
+
+ /**
+ * Returns true if the specified link satisfies the constraint.
+ *
+ * @param link link to be validated
+ * @param resourceService resource service for checking available link resources
+ * @return true if link is viable
+ */
+ public abstract boolean isValid(Link link, LinkResourceService resourceService);
+
+ /**
+ * {@inheritDoc}
+ *
+ * Negative return value means the specified link does not satisfy this constraint.
+ *
+ * @param link {@inheritDoc}
+ * @param resourceService {@inheritDoc}
+ * @return {@inheritDoc}
+ */
+ @Override
+ public double cost(Link link, LinkResourceService resourceService) {
+ return isValid(link, resourceService) ? +1 : -1;
+ }
+
+ @Override
+ public boolean validate(Path path, LinkResourceService resourceService) {
+ for (Link link : path.links()) {
+ if (!isValid(link, resourceService)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LambdaConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LambdaConstraint.java
new file mode 100644
index 00000000..9dd813b2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LambdaConstraint.java
@@ -0,0 +1,91 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.resource.link.LambdaResource;
+import org.onosproject.net.resource.link.LinkResourceService;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Constraint that evaluates links based on available lambda.
+ */
+@Beta
+public class LambdaConstraint extends BooleanConstraint {
+
+ private final LambdaResource lambda;
+
+ /**
+ * Creates a new optical lambda constraint.
+ *
+ * @param lambda optional lambda to indicate a specific lambda
+ */
+ public LambdaConstraint(LambdaResource lambda) {
+ this.lambda = lambda;
+ }
+
+ // Constructor for serialization
+ private LambdaConstraint() {
+ this.lambda = null;
+ }
+
+ @Override
+ public boolean isValid(Link link, LinkResourceService resourceService) {
+ for (ResourceRequest request : resourceService.getAvailableResources(link)) {
+ if (request.type() == ResourceType.LAMBDA) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the lambda required by this constraint.
+ *
+ * @return required lambda
+ */
+ public LambdaResource lambda() {
+ return lambda;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final LambdaConstraint other = (LambdaConstraint) obj;
+ return Objects.equals(this.lambda, other.lambda);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("lambda", lambda).toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LatencyConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LatencyConstraint.java
new file mode 100644
index 00000000..54eb4ea5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LatencyConstraint.java
@@ -0,0 +1,93 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Objects;
+
+import static org.onosproject.net.AnnotationKeys.LATENCY;
+import static org.onosproject.net.AnnotationKeys.getAnnotatedValue;
+
+/**
+ * Constraint that evaluates the latency through a path.
+ */
+@Beta
+public class LatencyConstraint implements Constraint {
+
+ private final Duration latency;
+
+ /**
+ * Creates a new constraint to keep under specified latency through a path.
+ * @param latency latency to be kept
+ */
+ public LatencyConstraint(Duration latency) {
+ this.latency = latency;
+ }
+
+ // Constructor for serialization
+ private LatencyConstraint() {
+ this.latency = Duration.ZERO;
+ }
+
+ public Duration latency() {
+ return latency;
+ }
+
+ @Override
+ public double cost(Link link, LinkResourceService resourceService) {
+ return getAnnotatedValue(link, LATENCY);
+ }
+
+ @Override
+ public boolean validate(Path path, LinkResourceService resourceService) {
+ double pathLatency = path.links().stream().mapToDouble(link -> cost(link, resourceService)).sum();
+ return Duration.of((long) pathLatency, ChronoUnit.MICROS).compareTo(latency) <= 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(latency);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof LatencyConstraint)) {
+ return false;
+ }
+
+ final LatencyConstraint that = (LatencyConstraint) obj;
+ return Objects.equals(this.latency, that.latency);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("latency", latency)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LinkTypeConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LinkTypeConstraint.java
new file mode 100644
index 00000000..ffa4405b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/LinkTypeConstraint.java
@@ -0,0 +1,108 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.Link;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Constraint that evaluates links based on their type.
+ */
+@Beta
+public class LinkTypeConstraint extends BooleanConstraint {
+
+ private final Set<Link.Type> types;
+ private final boolean isInclusive;
+
+ /**
+ * Creates a new constraint for requesting connectivity using or avoiding
+ * the specified link types.
+ *
+ * @param inclusive indicates whether the given link types are to be
+ * permitted or avoided
+ * @param types link types
+ */
+ public LinkTypeConstraint(boolean inclusive, Link.Type... types) {
+ checkNotNull(types, "Link types cannot be null");
+ checkArgument(types.length > 0, "There must be more than one type");
+ this.types = ImmutableSet.copyOf(types);
+ this.isInclusive = inclusive;
+ }
+
+ // Constructor for serialization
+ private LinkTypeConstraint() {
+ this.types = null;
+ this.isInclusive = false;
+ }
+
+ @Override
+ public boolean isValid(Link link, LinkResourceService resourceService) {
+ boolean contains = types.contains(link.type());
+ return isInclusive ? contains : !contains;
+ }
+
+ /**
+ * Returns the set of link types.
+ *
+ * @return set of link types
+ */
+ public Set<Link.Type> types() {
+ return types;
+ }
+
+ /**
+ * Indicates if the constraint is inclusive or exclusive.
+ *
+ * @return true if inclusive
+ */
+ public boolean isInclusive() {
+ return isInclusive;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(types, isInclusive);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final LinkTypeConstraint other = (LinkTypeConstraint) obj;
+ return Objects.equals(this.types, other.types) && Objects.equals(this.isInclusive, other.isInclusive);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("inclusive", isInclusive)
+ .add("types", types)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/ObstacleConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/ObstacleConstraint.java
new file mode 100644
index 00000000..cb1e6b2b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/ObstacleConstraint.java
@@ -0,0 +1,92 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Constraint that evaluates elements not passed through.
+ */
+@Beta
+public class ObstacleConstraint extends BooleanConstraint {
+
+ private final Set<DeviceId> obstacles;
+
+ /**
+ * Creates a new constraint that the specified device are not passed through.
+ * @param obstacles devices not to be passed
+ */
+ public ObstacleConstraint(DeviceId... obstacles) {
+ this.obstacles = ImmutableSet.copyOf(obstacles);
+ }
+
+ // Constructor for serialization
+ private ObstacleConstraint() {
+ this.obstacles = Collections.emptySet();
+ }
+
+ /**
+ * Returns the obstacle device ids.
+ *
+ * @return Set of obstacle device ids
+ */
+ public Set<DeviceId> obstacles() {
+ return obstacles;
+ }
+
+ @Override
+ public boolean isValid(Link link, LinkResourceService resourceService) {
+ DeviceId src = link.src().deviceId();
+ DeviceId dst = link.dst().deviceId();
+
+ return !(obstacles.contains(src) || obstacles.contains(dst));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(obstacles);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof ObstacleConstraint)) {
+ return false;
+ }
+
+ final ObstacleConstraint that = (ObstacleConstraint) obj;
+ return Objects.equals(this.obstacles, that.obstacles);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("obstacles", obstacles)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/PartialFailureConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/PartialFailureConstraint.java
new file mode 100644
index 00000000..827859b1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/PartialFailureConstraint.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.net.intent.constraint;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+/**
+ * A constraint that allows intents that can only be partially compiled
+ * (i.e. MultiPointToSinglePointIntent or SinglePointToMultiPointIntent)
+ * to be installed when some endpoints or paths are not found.
+ */
+public class PartialFailureConstraint implements Constraint {
+ @Override
+ public double cost(Link link, LinkResourceService resourceService) {
+ return 1;
+ }
+
+ @Override
+ public boolean validate(Path path, LinkResourceService resourceService) {
+ return true;
+ }
+
+ public static boolean intentAllowsPartialFailure(Intent intent) {
+ if (intent instanceof ConnectivityIntent) {
+ ConnectivityIntent connectivityIntent = (ConnectivityIntent) intent;
+ return connectivityIntent.constraints().stream()
+ .anyMatch(c -> c instanceof PartialFailureConstraint);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/WaypointConstraint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/WaypointConstraint.java
new file mode 100644
index 00000000..1acf6dfe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/WaypointConstraint.java
@@ -0,0 +1,117 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Constraint that evaluates elements passed through in order.
+ */
+@Beta
+public class WaypointConstraint implements Constraint {
+
+ private final List<DeviceId> waypoints;
+
+ /**
+ * Creates a new waypoint constraint.
+ *
+ * @param waypoints waypoints
+ */
+ public WaypointConstraint(DeviceId... waypoints) {
+ checkNotNull(waypoints, "waypoints cannot be null");
+ checkArgument(waypoints.length > 0, "length of waypoints should be more than 0");
+ this.waypoints = ImmutableList.copyOf(waypoints);
+ }
+
+ // Constructor for serialization
+ private WaypointConstraint() {
+ this.waypoints = Collections.emptyList();
+ }
+
+ public List<DeviceId> waypoints() {
+ return waypoints;
+ }
+
+ @Override
+ public double cost(Link link, LinkResourceService resourceService) {
+ // Always consider the number of hops
+ return 1;
+ }
+
+ @Override
+ public boolean validate(Path path, LinkResourceService resourceService) {
+ LinkedList<DeviceId> waypoints = new LinkedList<>(this.waypoints);
+ DeviceId current = waypoints.poll();
+ // This is safe because Path class ensures the number of links are more than 0
+ Link firstLink = path.links().get(0);
+ if (firstLink.src().elementId().equals(current)) {
+ current = waypoints.poll();
+ }
+
+ for (Link link : path.links()) {
+ if (link.dst().elementId().equals(current)) {
+ current = waypoints.poll();
+ // Empty waypoints means passing through all waypoints in the specified order
+ if (current == null) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(waypoints);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WaypointConstraint)) {
+ return false;
+ }
+
+ final WaypointConstraint that = (WaypointConstraint) obj;
+ return Objects.equals(this.waypoints, that.waypoints);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("waypoints", waypoints)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/package-info.java
new file mode 100644
index 00000000..60d8df16
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/constraint/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.
+ */
+
+/**
+ * Definitions of constraints used to refine intent specifications.
+ */
+package org.onosproject.net.intent.constraint;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/package-info.java
new file mode 100644
index 00000000..a86b3118
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/intent/package-info.java
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+/**
+ * Set of abstractions for conveying high-level intents for treatment of
+ * selected network traffic by allowing applications to express the
+ * <em>what</em> rather than the <em>how</em>. This makes such instructions
+ * largely independent of topology and device specifics, thus allowing them to
+ * survive topology mutations.
+ * <p>
+ * The controller core provides a suite of built-in intents and their compilers
+ * and installers. However, the intent framework is extensible in that it allows
+ * additional intents and their compilers or installers to be added
+ * dynamically at run-time. This allows others to enhance the initial arsenal of
+ * connectivity and policy-based intents available in base controller software.
+ * </p>
+ * <p>
+ * The following diagram depicts the state transition diagram for each top-level intent:<br>
+ * <img src="doc-files/intent-states.png" alt="ONOS intent states">
+ * </p>
+ * <p>
+ * The controller core accepts the intent specifications and translates them, via a
+ * process referred to as intent compilation, to installable intents, which are
+ * essentially actionable operations on the network environment.
+ * These actions are carried out by intent installation process, which results
+ * in some changes to the environment, e.g. tunnel links being provisioned,
+ * flow rules being installed on the data-plane, optical lambdas being reserved.
+ * </p>
+ * <p>
+ * After an intent is submitted by an application, it will be sent immediately
+ * (but asynchronously) into a compiling phase, then to installing phase and if
+ * all goes according to plan into installed state. Once an application decides
+ * it no longer wishes the intent to hold, it can withdraw it. This describes
+ * the nominal flow. However, it may happen that some issue is encountered.
+ * For example, an application may ask for an objective that is not currently
+ * achievable, e.g. connectivity across to unconnected network segments.
+ * If this is the case, the compiling phase may fail to produce a set of
+ * installable intents and instead result in a failed compile. If this occurs,
+ * only a change in the environment can trigger a transition back to the
+ * compiling state.
+ * </p>
+ * <p>
+ * Similarly, an issue may be encountered during the installation phase in
+ * which case the framework will attempt to recompile the intent to see if an
+ * alternate approach is available. If so, the intent will be sent back to
+ * installing phase. Otherwise, it will be parked in the failed state. Another
+ * scenario that’s very likely to be encountered is where the intent is
+ * successfully compiled and installed, but due to some topology event, such
+ * as a downed or downgraded link, loss of throughput may occur or connectivity
+ * may be lost altogether, thus impacting the viability of a previously
+ * satisfied intent. If this occurs, the framework will attempt to recompile
+ * the intent, and if an alternate approach is available, its installation
+ * will be attempted. Otherwise, the original top-level intent will be parked
+ * in the failed state.
+ * </p>
+ * <p>
+ * Please note that all *ing states, depicted in orange, are transitional and
+ * are expected to last only a brief amount of time. The rest of the states
+ * are parking states where the intent may spent some time; except for the
+ * submitted state of course. There, the intent may pause, but only briefly,
+ * while the system determines where to perform the compilation or while it
+ * performs global recomputation/optimization across all prior intents.
+ * </p>
+ * <p>
+ * The figure below depicts the general interactions between different
+ * components of the intent subsystem.<br>
+ * <img src="doc-files/intent-design.png" alt="ONOS intent subsystem design">
+ * </p>
+ */
+package org.onosproject.net.intent;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/DefaultLinkDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/DefaultLinkDescription.java
new file mode 100644
index 00000000..891eb65d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/DefaultLinkDescription.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.SparseAnnotations;
+
+/**
+ * Default implementation of immutable link description entity.
+ */
+public class DefaultLinkDescription extends AbstractDescription
+ implements LinkDescription {
+
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+ private final Link.Type type;
+
+ /**
+ * Creates a link description using the supplied information.
+ *
+ * @param src link source
+ * @param dst link destination
+ * @param type link type
+ * @param annotations optional key/value annotations
+ */
+ public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst,
+ Link.Type type, SparseAnnotations... annotations) {
+ super(annotations);
+ this.src = src;
+ this.dst = dst;
+ this.type = type;
+ }
+
+ @Override
+ public ConnectPoint src() {
+ return src;
+ }
+
+ @Override
+ public ConnectPoint dst() {
+ return dst;
+ }
+
+ @Override
+ public Link.Type type() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("src", src())
+ .add("dst", dst())
+ .add("type", type()).toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkAdminService.java
new file mode 100644
index 00000000..a0b5e1e2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkAdminService.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Service for administering the inventory of infrastructure links.
+ */
+public interface LinkAdminService extends LinkService {
+
+ /**
+ * Removes all infrastructure links leading to and from the
+ * specified connection point.
+ *
+ * @param connectPoint connection point
+ */
+ void removeLinks(ConnectPoint connectPoint);
+
+ /**
+ * Removes all infrastructure links leading to and from the
+ * specified device.
+ *
+ * @param deviceId device identifier
+ */
+ void removeLinks(DeviceId deviceId);
+
+ /**
+ * Removes all links between between the specified src and
+ * dst connection points.
+ *
+ * @param src link source
+ * @param dst link destination
+ */
+ void removeLink(ConnectPoint src, ConnectPoint dst);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkDescription.java
new file mode 100644
index 00000000..f85718b7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkDescription.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Description;
+import org.onosproject.net.Link;
+
+/**
+ * Describes an infrastructure link.
+ */
+public interface LinkDescription extends Description {
+
+ /**
+ * Returns the link source.
+ *
+ * @return links source
+ */
+ ConnectPoint src();
+
+ /**
+ * Returns the link destination.
+ *
+ * @return links destination
+ */
+ ConnectPoint dst();
+
+ /**
+ * Returns the link type.
+ *
+ * @return link type
+ */
+ Link.Type type();
+
+ // Add further link attributes
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkEvent.java
new file mode 100644
index 00000000..d87bce06
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkEvent.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.Link;
+
+/**
+ * Describes infrastructure link event.
+ */
+public class LinkEvent extends AbstractEvent<LinkEvent.Type, Link> {
+
+ /**
+ * Type of link events.
+ */
+ public enum Type {
+ /**
+ * Signifies that a new link has been detected.
+ */
+ LINK_ADDED,
+
+ /**
+ * Signifies that a link has been updated or changed state.
+ */
+ LINK_UPDATED,
+
+ /**
+ * Signifies that a link has been removed.
+ */
+ LINK_REMOVED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified link and the
+ * current time.
+ *
+ * @param type link event type
+ * @param link event link subject
+ */
+ public LinkEvent(Type type, Link link) {
+ super(type, link);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified link and time.
+ *
+ * @param type link event type
+ * @param link event link subject
+ * @param time occurrence time
+ */
+ public LinkEvent(Type type, Link link, long time) {
+ super(type, link, time);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkListener.java
new file mode 100644
index 00000000..82f6bdb9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving infrastructure link related events.
+ */
+public interface LinkListener extends EventListener<LinkEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProvider.java
new file mode 100644
index 00000000..ed4348c7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProvider.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of an entity providing information about infrastructure links
+ * to the core.
+ */
+public interface LinkProvider extends Provider {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderRegistry.java
new file mode 100644
index 00000000..57a05d93
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction of an infrastructure link provider registry.
+ */
+public interface LinkProviderRegistry
+ extends ProviderRegistry<LinkProvider, LinkProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderService.java
new file mode 100644
index 00000000..f5ef52a2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkProviderService.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderService;
+
+/**
+ * Means for injecting link information into the core.
+ */
+public interface LinkProviderService extends ProviderService<LinkProvider> {
+
+ /**
+ * Signals that an infrastructure link has been detected.
+ *
+ * @param linkDescription link information
+ */
+ void linkDetected(LinkDescription linkDescription);
+
+ /**
+ * Signals that an infrastructure link has disappeared.
+ *
+ * @param linkDescription link information
+ */
+ void linkVanished(LinkDescription linkDescription);
+
+ /**
+ * Signals that infrastructure links associated with the specified
+ * connect point have vanished.
+ *
+ * @param connectPoint connect point
+ */
+ void linksVanished(ConnectPoint connectPoint);
+
+ /**
+ * Signals that infrastructure links associated with the specified
+ * device have vanished.
+ *
+ * @param deviceId device identifier
+ */
+ void linksVanished(DeviceId deviceId);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkService.java
new file mode 100644
index 00000000..c27e3110
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkService.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+
+/**
+ * Service for interacting with the inventory of infrastructure links.
+ */
+public interface LinkService
+ extends ListenerService<LinkEvent, LinkListener> {
+
+ /**
+ * Returns the count of all known infrastructure links.
+ *
+ * @return number of infrastructure links
+ */
+ int getLinkCount();
+
+ /**
+ * Returns a collection of all known infrastructure links.
+ *
+ * @return all infrastructure links
+ */
+ Iterable<Link> getLinks();
+
+ /**
+ * Returns a collection of all active infrastructure links.
+ *
+ * @return all infrastructure links
+ */
+ Iterable<Link> getActiveLinks();
+
+ /**
+ * Returns set of all infrastructure links leading to and from the
+ * specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device links
+ */
+ Set<Link> getDeviceLinks(DeviceId deviceId);
+
+ /**
+ * Returns set of all infrastructure links leading from the specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device egress links
+ */
+ Set<Link> getDeviceEgressLinks(DeviceId deviceId);
+
+ /**
+ * Returns set of all infrastructure links leading to the specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device ingress links
+ */
+ Set<Link> getDeviceIngressLinks(DeviceId deviceId);
+
+ /**
+ * Returns set of all infrastructure links leading to and from the
+ * specified connection point.
+ *
+ * @param connectPoint connection point
+ * @return set of links
+ */
+ Set<Link> getLinks(ConnectPoint connectPoint);
+
+ /**
+ * Returns set of all infrastructure links leading from the specified
+ * connection point.
+ *
+ * @param connectPoint connection point
+ * @return set of device egress links
+ */
+ Set<Link> getEgressLinks(ConnectPoint connectPoint);
+
+ /**
+ * Returns set of all infrastructure links leading to the specified
+ * connection point.
+ *
+ * @param connectPoint connection point
+ * @return set of device ingress links
+ */
+ Set<Link> getIngressLinks(ConnectPoint connectPoint);
+
+ // FIXME: I don't think this makes sense; discuss and remove or adjust return
+ // to be a Set<Link> or add Link.Type parameter
+ /**
+ * Returns the infrastructure links between the specified source
+ * and destination connection points.
+ *
+ * @param src source connection point
+ * @param dst destination connection point
+ * @return link from source to destination; null if none found
+ */
+ Link getLink(ConnectPoint src, ConnectPoint dst);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStore.java
new file mode 100644
index 00000000..04c8773b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStore.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Manages inventory of infrastructure links; not intended for direct use.
+ */
+public interface LinkStore extends Store<LinkEvent, LinkStoreDelegate> {
+
+ /**
+ * Returns the number of links in the store.
+ *
+ * @return number of links
+ */
+ int getLinkCount();
+
+ /**
+ * Returns an iterable collection of all links in the inventory.
+ *
+ * @return collection of all links
+ */
+ Iterable<Link> getLinks();
+
+ /**
+ * Returns all links egressing from the specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device links
+ */
+ Set<Link> getDeviceEgressLinks(DeviceId deviceId);
+
+ /**
+ * Returns all links ingressing from the specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device links
+ */
+ Set<Link> getDeviceIngressLinks(DeviceId deviceId);
+
+ /**
+ * Returns the link between the two end-points.
+ *
+ * @param src source connection point
+ * @param dst destination connection point
+ * @return link or null if one not found between the end-points
+ */
+ Link getLink(ConnectPoint src, ConnectPoint dst);
+
+ /**
+ * Returns all links egressing from the specified connection point.
+ *
+ * @param src source connection point
+ * @return set of connection point links
+ */
+ Set<Link> getEgressLinks(ConnectPoint src);
+
+ /**
+ * Returns all links ingressing to the specified connection point.
+ *
+ * @param dst destination connection point
+ * @return set of connection point links
+ */
+ Set<Link> getIngressLinks(ConnectPoint dst);
+
+ /**
+ * Creates a new link, or updates an existing one, based on the given
+ * information.
+ *
+ * @param providerId provider identity
+ * @param linkDescription link description
+ * @return create or update link event, or null if no change resulted
+ */
+ LinkEvent createOrUpdateLink(ProviderId providerId,
+ LinkDescription linkDescription);
+
+ /**
+ * Removes the link, or marks it as inactive if the link is durable,
+ * based on the specified information.
+ *
+ * @param src link source
+ * @param dst link destination
+ * @return remove or update link event, or null if no change resulted
+ */
+ LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst);
+
+ /**
+ * Removes the link based on the specified information.
+ *
+ * @param src link source
+ * @param dst link destination
+ * @return remove link event, or null if no change resulted
+ */
+ LinkEvent removeLink(ConnectPoint src, ConnectPoint dst);
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStoreDelegate.java
new file mode 100644
index 00000000..1f66dd49
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/LinkStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Infrastructure link store delegate abstraction.
+ */
+public interface LinkStoreDelegate extends StoreDelegate<LinkEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/package-info.java
new file mode 100644
index 00000000..57aa5fa2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/link/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.
+ */
+
+/**
+ * Infrastructure link model &amp; related services API definitions.
+ */
+package org.onosproject.net.link;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Band.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Band.java
new file mode 100644
index 00000000..2bfafad2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Band.java
@@ -0,0 +1,133 @@
+/*
+ * 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.meter;
+
+/**
+ * Represents a band used within a meter.
+ */
+public interface Band {
+
+ /**
+ * Specifies the type of band.
+ */
+ enum Type {
+ /**
+ * Simple rate limiter which drops packets
+ * when the rate is exceeded.
+ */
+ DROP,
+
+ /**
+ * defines a simple DiffServ policer that remark
+ * the drop precedence of the DSCP field in the
+ * IP header of the packets that exceed the band
+ * rate value.
+ */
+ REMARK
+ }
+
+ /**
+ * The rate at which this meter applies.
+ *
+ * @return the long value of the rate
+ */
+ long rate();
+
+ /**
+ * The burst size at which the meter applies.
+ *
+ * @return the long value of the size
+ */
+ long burst();
+
+ /**
+ * Only meaningful in the case of a REMARK band type.
+ * indicates by which amount the drop precedence of
+ * the packet should be increase if the band is exceeded.
+ *
+ * @return a short value
+ */
+ short dropPrecedence();
+
+ /**
+ * Signals the type of band to create.
+ *
+ * @return a band type
+ */
+ Type type();
+
+ /**
+ * Returns the packets seen by this band.
+ *
+ * @return a long value
+ */
+ long packets();
+
+ /**
+ * Return the bytes seen by this band.
+ *
+ * @return a byte counter
+ */
+ long bytes();
+
+ interface Builder {
+
+ /**
+ * Assigns a rate to this band. The units for this rate
+ * are defined in the encapsulating meter.
+ *
+ * @param rate a long value
+ * @return this
+ */
+ Builder withRate(long rate);
+
+ /**
+ * Assigns a burst size to this band. Only meaningful if
+ * the encapsulating meter is of burst type.
+ *
+ * @param burstSize a long value.
+ * @return this
+ */
+ Builder burstSize(long burstSize);
+
+ /**
+ * Assigns the drop precedence for this band. Only meaningful if
+ * the band is of REMARK type.
+ *
+ * @param prec a short value
+ * @return this
+ */
+ Builder dropPrecedence(short prec);
+
+ /**
+ * Assigns the @See Type of this band.
+ *
+ * @param type a band type
+ * @return this
+ */
+ Builder ofType(Type type);
+
+ /**
+ * Builds the band.
+ *
+ * @return a band
+ */
+ Band build();
+
+ }
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/BandEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/BandEntry.java
new file mode 100644
index 00000000..03145e91
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/BandEntry.java
@@ -0,0 +1,37 @@
+/*
+ * 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.meter;
+
+/**
+ * Represents a stored band.
+ */
+public interface BandEntry extends Band {
+
+ /**
+ * Sets the number of packets seen by this band.
+ *
+ * @param packets a packet count
+ */
+ void setPackets(long packets);
+
+ /**
+ * Sets the number of bytes seen by this band.
+ *
+ * @param bytes a byte counter
+ */
+ void setBytes(long bytes);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultBand.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultBand.java
new file mode 100644
index 00000000..58a24766
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultBand.java
@@ -0,0 +1,136 @@
+/*
+ * 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.meter;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A default implementation for a Band.
+ */
+public final class DefaultBand implements Band, BandEntry {
+
+ private final Type type;
+ private final long rate;
+ //TODO: should be made optional
+ private final Long burstSize;
+ private final Short prec;
+ private long packets;
+ private long bytes;
+
+ public DefaultBand(Type type, long rate,
+ Long burstSize, Short prec) {
+ this.type = type;
+ this.rate = rate;
+ this.burstSize = burstSize;
+ this.prec = prec;
+ }
+
+ @Override
+ public long rate() {
+ return rate;
+ }
+
+ @Override
+ public long burst() {
+ return burstSize;
+ }
+
+ @Override
+ public short dropPrecedence() {
+ return prec;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public long packets() {
+ return packets;
+ }
+
+ @Override
+ public long bytes() {
+ return bytes;
+ }
+
+ @Override
+ public void setPackets(long packets) {
+ this.packets = packets;
+ }
+
+ @Override
+ public void setBytes(long bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("rate", rate)
+ .add("burst-size", burstSize)
+ .add("type", type)
+ .add("drop-precedence", prec).toString();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder implements Band.Builder {
+
+ private long rate;
+ private Long burstSize;
+ private Short prec;
+ private Type type;
+
+ @Override
+ public Band.Builder withRate(long rate) {
+ this.rate = rate;
+ return this;
+ }
+
+ @Override
+ public Band.Builder burstSize(long burstSize) {
+ this.burstSize = burstSize;
+ return this;
+ }
+
+ @Override
+ public Band.Builder dropPrecedence(short prec) {
+ this.prec = prec;
+ return this;
+ }
+
+ @Override
+ public Band.Builder ofType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public DefaultBand build() {
+ checkArgument(type != Type.REMARK && prec == null,
+ "Only REMARK bands can have a precendence.");
+
+ return new DefaultBand(type, rate, burstSize, prec);
+ }
+
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java
new file mode 100644
index 00000000..f7d6210d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeter.java
@@ -0,0 +1,233 @@
+/*
+ * 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.meter;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collection;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A default implementation of a meter.
+ */
+public final class DefaultMeter implements Meter, MeterEntry {
+
+
+ private final MeterId id;
+ private final ApplicationId appId;
+ private final Unit unit;
+ private final boolean burst;
+ private final Collection<Band> bands;
+ private final DeviceId deviceId;
+
+ private MeterState state;
+ private long life;
+ private long refCount;
+ private long packets;
+ private long bytes;
+
+ private DefaultMeter(DeviceId deviceId, MeterId id, ApplicationId appId,
+ Unit unit, boolean burst,
+ Collection<Band> bands) {
+ this.deviceId = deviceId;
+ this.id = id;
+ this.appId = appId;
+ this.unit = unit;
+ this.burst = burst;
+ this.bands = bands;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public MeterId id() {
+ return id;
+ }
+
+ @Override
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
+ public Unit unit() {
+ return unit;
+ }
+
+ @Override
+ public boolean isBurst() {
+ return burst;
+ }
+
+ @Override
+ public Collection<Band> bands() {
+ return bands;
+ }
+
+ @Override
+ public MeterState state() {
+ return state;
+ }
+
+ @Override
+ public long life() {
+ return life;
+ }
+
+ @Override
+ public long referenceCount() {
+ return refCount;
+ }
+
+ @Override
+ public long packetsSeen() {
+ return packets;
+ }
+
+ @Override
+ public long bytesSeen() {
+ return bytes;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public void setState(MeterState state) {
+ this.state = state;
+ }
+
+ @Override
+ public void setLife(long life) {
+ this.life = life;
+ }
+
+ @Override
+ public void setReferenceCount(long count) {
+ this.refCount = count;
+ }
+
+ @Override
+ public void setProcessedPackets(long packets) {
+ this.packets = packets;
+ }
+
+ @Override
+ public void setProcessedBytes(long bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("device", deviceId)
+ .add("id", id)
+ .add("appId", appId.name())
+ .add("unit", unit)
+ .add("isBurst", burst)
+ .add("state", state)
+ .add("bands", bands).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DefaultMeter that = (DefaultMeter) o;
+ return Objects.equal(id, that.id) &&
+ Objects.equal(appId, that.appId) &&
+ Objects.equal(unit, that.unit) &&
+ Objects.equal(deviceId, that.deviceId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id, appId, unit, deviceId);
+ }
+
+ public static final class Builder implements Meter.Builder {
+
+ private MeterId id;
+ private ApplicationId appId;
+ private Unit unit = Unit.KB_PER_SEC;
+ private boolean burst = false;
+ private Collection<Band> bands;
+ private DeviceId deviceId;
+
+
+ @Override
+ public Meter.Builder forDevice(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ return this;
+ }
+
+ @Override
+ public Meter.Builder withId(MeterId id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ public Meter.Builder fromApp(ApplicationId appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ @Override
+ public Meter.Builder withUnit(Unit unit) {
+ this.unit = unit;
+ return this;
+ }
+
+ @Override
+ public Meter.Builder burst() {
+ this.burst = true;
+ return this;
+ }
+
+ @Override
+ public Meter.Builder withBands(Collection<Band> bands) {
+ this.bands = ImmutableSet.copyOf(bands);
+ return this;
+ }
+
+ @Override
+ public DefaultMeter build() {
+ checkNotNull(deviceId, "Must specify a device");
+ checkNotNull(bands, "Must have bands.");
+ checkArgument(bands.size() > 0, "Must have at least one band.");
+ checkNotNull(appId, "Must have an application id");
+ checkNotNull(id, "Must specify a meter id");
+ return new DefaultMeter(deviceId, id, appId, unit, burst, bands);
+ }
+
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterRequest.java
new file mode 100644
index 00000000..94cada47
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/DefaultMeterRequest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.meter;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A default implementation of a meter.
+ */
+public final class DefaultMeterRequest implements MeterRequest {
+
+
+
+ private final ApplicationId appId;
+ private final Meter.Unit unit;
+ private final boolean burst;
+ private final Collection<Band> bands;
+ private final DeviceId deviceId;
+ private final Optional<MeterContext> context;
+ private final Type op;
+
+ private DefaultMeterRequest(DeviceId deviceId, ApplicationId appId,
+ Meter.Unit unit, boolean burst,
+ Collection<Band> bands, MeterContext context, Type op) {
+ this.deviceId = deviceId;
+ this.appId = appId;
+ this.unit = unit;
+ this.burst = burst;
+ this.bands = bands;
+ this.context = Optional.ofNullable(context);
+ this.op = op;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
+ public Meter.Unit unit() {
+ return unit;
+ }
+
+ @Override
+ public boolean isBurst() {
+ return burst;
+ }
+
+ @Override
+ public Collection<Band> bands() {
+ return bands;
+ }
+
+ @Override
+ public Optional<MeterContext> context() {
+ return context;
+ }
+
+
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("device", deviceId)
+ .add("appId", appId.name())
+ .add("unit", unit)
+ .add("isBurst", burst)
+ .add("bands", bands).toString();
+ }
+
+ public static final class Builder implements MeterRequest.Builder {
+
+ private ApplicationId appId;
+ private Meter.Unit unit = Meter.Unit.KB_PER_SEC;
+ private boolean burst = false;
+ private Collection<Band> bands;
+ private DeviceId deviceId;
+ private MeterContext context;
+
+
+ @Override
+ public MeterRequest.Builder forDevice(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ return this;
+ }
+
+ @Override
+ public MeterRequest.Builder fromApp(ApplicationId appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ @Override
+ public MeterRequest.Builder withUnit(Meter.Unit unit) {
+ this.unit = unit;
+ return this;
+ }
+
+ @Override
+ public MeterRequest.Builder burst() {
+ this.burst = true;
+ return this;
+ }
+
+ @Override
+ public MeterRequest.Builder withBands(Collection<Band> bands) {
+ this.bands = ImmutableSet.copyOf(bands);
+ return this;
+ }
+
+ @Override
+ public MeterRequest.Builder withContext(MeterContext context) {
+ this.context = context;
+ return this;
+ }
+
+ @Override
+ public MeterRequest add() {
+ validate();
+ return new DefaultMeterRequest(deviceId, appId, unit, burst, bands,
+ context, Type.ADD);
+ }
+
+ @Override
+ public MeterRequest remove() {
+ validate();
+ return new DefaultMeterRequest(deviceId, appId, unit, burst, bands,
+ context, Type.REMOVE);
+ }
+
+ private void validate() {
+ checkNotNull(deviceId, "Must specify a device");
+ checkNotNull(bands, "Must have bands.");
+ checkArgument(bands.size() > 0, "Must have at least one band.");
+ checkNotNull(appId, "Must have an application id");
+ }
+
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Meter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Meter.java
new file mode 100644
index 00000000..98ebc350
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/Meter.java
@@ -0,0 +1,179 @@
+/*
+ * 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.meter;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collection;
+
+/**
+ * Represents a generalized meter to be deployed on a device.
+ */
+public interface Meter {
+
+ enum Unit {
+ /**
+ * Packets per second.
+ */
+ PKTS_PER_SEC,
+
+ /**
+ * Kilo bits per second.
+ */
+ KB_PER_SEC
+ }
+
+ /**
+ * The target device for this meter.
+ *
+ * @return a device id
+ */
+ DeviceId deviceId();
+
+ /**
+ * This meters id.
+ *
+ * @return a meter id
+ */
+ MeterId id();
+
+ /**
+ * The id of the application which created this meter.
+ *
+ * @return an application id
+ */
+ ApplicationId appId();
+
+ /**
+ * The unit used within this meter.
+ *
+ * @return the unit
+ */
+ Unit unit();
+
+ /**
+ * Signals whether this meter applies to bursts only.
+ *
+ * @return a boolean
+ */
+ boolean isBurst();
+
+ /**
+ * The collection of bands to apply on the dataplane.
+ *
+ * @return a collection of bands.
+ */
+ Collection<Band> bands();
+
+ /**
+ * Fetches the state of this meter.
+ *
+ * @return a meter state
+ */
+ MeterState state();
+
+ /**
+ * The lifetime in seconds of this meter.
+ *
+ * @return number of seconds
+ */
+ long life();
+
+ /**
+ * The number of flows pointing to this meter.
+ *
+ * @return a reference count
+ */
+ long referenceCount();
+
+ /**
+ * Number of packets processed by this meter.
+ *
+ * @return a packet count
+ */
+ long packetsSeen();
+
+ /**
+ * Number of bytes processed by this meter.
+ *
+ * @return a byte count
+ */
+ long bytesSeen();
+
+ /**
+ * A meter builder.
+ */
+ interface Builder {
+
+ /**
+ * Assigns the target device for this meter.
+ *
+ * @param deviceId a device id
+ * @return this
+ */
+ Builder forDevice(DeviceId deviceId);
+
+ /**
+ * Assigns the id to this meter.
+ *
+ * @param id a e
+ * @return this
+ */
+ Builder withId(MeterId id);
+
+ /**
+ * Assigns the application that built this meter.
+ *
+ * @param appId an application id
+ * @return this
+ */
+ Builder fromApp(ApplicationId appId);
+
+ /**
+ * Assigns the @See Unit to use for this meter.
+ * Defaults to kb/s
+ *
+ * @param unit a unit
+ * @return this
+ */
+ Builder withUnit(Unit unit);
+
+ /**
+ * Sets this meter as applicable to burst traffic only.
+ * Defaults to false.
+ *
+ * @return this
+ */
+ Builder burst();
+
+ /**
+ * Assigns bands to this meter. There must be at least one band.
+ *
+ * @param bands a collection of bands
+ * @return this
+ */
+ Builder withBands(Collection<Band> bands);
+
+ /**
+ * Builds the meter based on the specified parameters.
+ *
+ * @return a meter
+ */
+ Meter build();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterContext.java
new file mode 100644
index 00000000..574bdca9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterContext.java
@@ -0,0 +1,38 @@
+/*
+ * 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.meter;
+
+/**
+ * A context permitting the application to be notified when the
+ * meter installation has been successful.
+ */
+public interface MeterContext {
+
+ /**
+ * Invoked on successful installation of the meter.
+ *
+ * @param op a meter
+ */
+ default void onSuccess(MeterRequest op) {}
+
+ /**
+ * Invoked when error is encountered while installing a meter.
+ *
+ * @param op a meter
+ * @param reason the reason why it failed
+ */
+ default void onError(MeterRequest op, MeterFailReason reason) {}
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEntry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEntry.java
new file mode 100644
index 00000000..178a564c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEntry.java
@@ -0,0 +1,57 @@
+/*
+ * 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.meter;
+
+/**
+ * Represents a stored meter.
+ */
+public interface MeterEntry extends Meter {
+
+ /**
+ * Updates the state of this meter.
+ *
+ * @param state a meter state
+ */
+ void setState(MeterState state);
+
+ /**
+ * Set the amount of time the meter has existed in seconds.
+ *
+ * @param life number of seconds
+ */
+ void setLife(long life);
+
+ /**
+ * Sets the number of flows which are using this meter.
+ *
+ * @param count a reference count.
+ */
+ void setReferenceCount(long count);
+
+ /**
+ * Updates the number of packets seen by this meter.
+ *
+ * @param packets a packet count.
+ */
+ void setProcessedPackets(long packets);
+
+ /**
+ * Updates the number of bytes seen by the meter.
+ *
+ * @param bytes a byte counter.
+ */
+ void setProcessedBytes(long bytes);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEvent.java
new file mode 100644
index 00000000..72f0a53a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterEvent.java
@@ -0,0 +1,62 @@
+/*
+ * 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.meter;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Entity that represents Meter events.
+ */
+public class MeterEvent extends AbstractEvent<MeterEvent.Type, Meter> {
+
+
+ public enum Type {
+ /**
+ * A meter addition was requested.
+ */
+ METER_ADD_REQ,
+
+ /**
+ * A meter removal was requested.
+ */
+ METER_REM_REQ
+ }
+
+
+ /**
+ * Creates an event of a given type and for the specified meter and the
+ * current time.
+ *
+ * @param type meter event type
+ * @param meter event subject
+ */
+ public MeterEvent(Type type, Meter meter) {
+ super(type, meter);
+ }
+
+ /**
+ * Creates an event of a given type and for the specified meter and time.
+ *
+ * @param type meter event type
+ * @param meter event subject
+ * @param time occurrence time
+ */
+ public MeterEvent(Type type, Meter meter, long time) {
+ super(type, meter, time);
+ }
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterFailReason.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterFailReason.java
new file mode 100644
index 00000000..8683e2a2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterFailReason.java
@@ -0,0 +1,89 @@
+/*
+ * 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.meter;
+
+/**
+ * Enum used to represent a meter failure condition.
+ */
+public enum MeterFailReason {
+ /**
+ * A meter with the same identifier already exists.
+ * Essentially a duplicate meter exists.
+ */
+ EXISTING_METER,
+
+ /**
+ * The device does not support any more meters.
+ */
+ OUT_OF_METERS,
+
+ /**
+ * The device does not support any more bands for this meter.
+ */
+ OUT_OF_BANDS,
+
+ /**
+ * The meter that was attempted to be modified is unknown.
+ */
+ UNKNOWN,
+
+ /**
+ * The operation for this meter installation timed out.
+ */
+ TIMEOUT,
+
+ /**
+ * Invalid meter definition.
+ */
+ INVALID_METER,
+
+ /**
+ * The target device is unknown.
+ */
+ UNKNOWN_DEVICE,
+
+ /**
+ * Unknown command.
+ */
+ UNKNOWN_COMMAND,
+
+ /**
+ * Unknown flags.
+ */
+ UNKNOWN_FLAGS,
+
+ /**
+ * Bad rate value.
+ */
+ BAD_RATE,
+
+ /**
+ * Bad burst size value.
+ */
+ BAD_BURST,
+
+ /**
+ * Bad band.
+ */
+ BAD_BAND,
+
+ /**
+ * Bad value value.
+ */
+ BAD_BAND_VALUE
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterId.java
new file mode 100644
index 00000000..872de2d8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterId.java
@@ -0,0 +1,78 @@
+/*
+ * 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.meter;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A representation of a meter id.
+ * Uniquely identifies a meter system wide.
+ */
+public final class MeterId {
+
+ static final long MAX = 0xFFFF0000;
+
+ private final long id;
+
+ public static final MeterId SLOWPATH = new MeterId(0xFFFFFFFD);
+ public static final MeterId CONTROLLER = new MeterId(0xFFFFFFFE);
+ public static final MeterId ALL = new MeterId(0xFFFFFFFF);
+
+ private MeterId(long id) {
+ checkArgument(id >= MAX, "id cannot be larger than 0xFFFF0000");
+ this.id = id;
+ }
+
+ /**
+ * The integer representation of the meter id.
+ *
+ * @return a long
+ */
+ public long id() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ MeterId meterId = (MeterId) o;
+
+ return id == meterId.id;
+
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(id);
+ }
+
+ @Override
+ public String toString() {
+ return Long.toHexString(this.id);
+ }
+
+ public static MeterId meterId(long id) {
+ return new MeterId(id);
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterListener.java
new file mode 100644
index 00000000..0a5e203f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.meter;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving Meter related events.
+ */
+public interface MeterListener extends EventListener<MeterEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperation.java
new file mode 100644
index 00000000..437dd269
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperation.java
@@ -0,0 +1,88 @@
+/*
+ * 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.meter;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Representation of an operation on the meter table.
+ */
+public class MeterOperation {
+
+
+ /**
+ * Tyoe of meter operation.
+ */
+ public enum Type {
+ ADD,
+ REMOVE,
+ MODIFY
+ }
+
+ private final Meter meter;
+ private final Type type;
+
+
+ public MeterOperation(Meter meter, Type type) {
+ this.meter = meter;
+ this.type = type;
+ }
+
+ /**
+ * Returns the type of operation.
+ *
+ * @return type
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the meter.
+ *
+ * @return a meter
+ */
+ public Meter meter() {
+ return meter;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("meter", meter)
+ .add("type", type)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MeterOperation that = (MeterOperation) o;
+ return Objects.equal(meter, that.meter) &&
+ Objects.equal(type, that.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(meter, type);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperations.java
new file mode 100644
index 00000000..92b0c3aa
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterOperations.java
@@ -0,0 +1,50 @@
+/*
+ * 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.meter;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Immutable collection of meter operation to be used between
+ * core and provider layers of group subsystem.
+ *
+ */
+public final class MeterOperations {
+ private final List<MeterOperation> operations;
+
+ /**
+ * Creates a immutable list of meter operation.
+ *
+ * @param operations list of meter operation
+ */
+ public MeterOperations(List<MeterOperation> operations) {
+ this.operations = ImmutableList.copyOf(checkNotNull(operations));
+ }
+
+ /**
+ * Returns immutable list of Meter operation.
+ *
+ * @return list of Meter operation
+ */
+ public List<MeterOperation> operations() {
+ return operations;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProvider.java
new file mode 100644
index 00000000..4655e234
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProvider.java
@@ -0,0 +1,48 @@
+/*
+ * 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.meter;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of a Meter provider.
+ */
+public interface MeterProvider extends Provider {
+
+ /**
+ * Performs a batch of meter operation on the specified device with the
+ * specified parameters.
+ *
+ * @param deviceId device identifier on which the batch of group
+ * operations to be executed
+ * @param meterOps immutable list of meter operation
+ */
+ void performMeterOperation(DeviceId deviceId,
+ MeterOperations meterOps);
+
+
+ /**
+ * Performs a meter operation on the specified device with the
+ * specified parameters.
+ *
+ * @param deviceId device identifier on which the batch of group
+ * operations to be executed
+ * @param meterOp a meter operation
+ */
+ void performMeterOperation(DeviceId deviceId,
+ MeterOperation meterOp);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderRegistry.java
new file mode 100644
index 00000000..019ca19a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderRegistry.java
@@ -0,0 +1,27 @@
+/*
+ * 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.meter;
+
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction for a meter provider registry.
+ */
+public interface MeterProviderRegistry
+ extends ProviderRegistry<MeterProvider, MeterProviderService> {
+}
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.java
new file mode 100644
index 00000000..85c0c43e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterProviderService.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.net.meter;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderService;
+
+import java.util.Collection;
+
+/**
+ * Service through which meter providers can inject information
+ * into the core.
+ */
+public interface MeterProviderService extends ProviderService<MeterProvider> {
+
+ /**
+ * Notifies the core that a meter operaton failed for a
+ * specific reason.
+ * @param operation the failed operation
+ * @param reason the failure reason
+ */
+ void meterOperationFailed(MeterOperation operation,
+ MeterFailReason reason);
+
+ /**
+ * Pushes the collection of meters observed on the data plane as
+ * well as their associated statistics.
+ *
+ * @param deviceId a device id
+ * @param meterEntries a collection of meter entries
+ */
+ void pushMeterMetrics(DeviceId deviceId,
+ Collection<Meter> meterEntries);
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.java
new file mode 100644
index 00000000..fd11ca41
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterRequest.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.meter;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Represents a generalized meter request to be deployed on a device.
+ */
+public interface MeterRequest {
+
+ enum Type {
+ ADD,
+ MODIFY,
+ REMOVE
+ }
+
+ /**
+ * The target device for this meter.
+ *
+ * @return a device id
+ */
+ DeviceId deviceId();
+
+ /**
+ * The id of the application which created this meter.
+ *
+ * @return an application id
+ */
+ ApplicationId appId();
+
+ /**
+ * The unit used within this meter.
+ *
+ * @return the unit
+ */
+ Meter.Unit unit();
+
+ /**
+ * Signals whether this meter applies to bursts only.
+ *
+ * @return a boolean
+ */
+ boolean isBurst();
+
+ /**
+ * The collection of bands to apply on the dataplane.
+ *
+ * @return a collection of bands.
+ */
+ Collection<Band> bands();
+
+ /**
+ * Returns the callback context for this meter.
+ *
+ * @return an optional meter context
+ */
+ Optional<MeterContext> context();
+
+ /**
+ * A meter builder.
+ */
+ interface Builder {
+
+ /**
+ * Assigns the target device for this meter.
+ *
+ * @param deviceId a device id
+ * @return this
+ */
+ Builder forDevice(DeviceId deviceId);
+
+ /**
+ * Assigns the application that built this meter.
+ *
+ * @param appId an application id
+ * @return this
+ */
+ Builder fromApp(ApplicationId appId);
+
+ /**
+ * Assigns the @See Unit to use for this meter.
+ * Defaults to kb/s
+ *
+ * @param unit a unit
+ * @return this
+ */
+ Builder withUnit(Meter.Unit unit);
+
+ /**
+ * Sets this meter as applicable to burst traffic only.
+ * Defaults to false.
+ *
+ * @return this
+ */
+ Builder burst();
+
+ /**
+ * Assigns bands to this meter. There must be at least one band.
+ *
+ * @param bands a collection of bands
+ * @return this
+ */
+ Builder withBands(Collection<Band> bands);
+
+ /**
+ * Assigns an execution context for this meter request.
+ *
+ * @param context a meter context
+ * @return this
+ */
+ Builder withContext(MeterContext context);
+
+ /**
+ * Requests the addition of a meter.
+ *
+ * @return a meter request
+ */
+ MeterRequest add();
+
+ /**
+ * Requests the removal of a meter.
+ *
+ * @return a meter request
+ */
+ MeterRequest remove();
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterService.java
new file mode 100644
index 00000000..bdc90eb7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterService.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.meter;
+
+import org.onosproject.event.ListenerService;
+
+import java.util.Collection;
+
+/**
+ * Service for add/updating and removing meters. Meters are
+ * are assigned to flow to rate limit them and provide a certain
+ * quality of service.
+ */
+public interface MeterService
+ extends ListenerService<MeterEvent, MeterListener> {
+
+ /**
+ * Adds a meter to the system and performs it installation.
+ *
+ * @param meter a meter
+ * @return a meter (with a meter id)
+ */
+ Meter submit(MeterRequest meter);
+
+ /**
+ * Remove a meter from the system and the dataplane.
+ *
+ * @param meter a meter to remove
+ * @param meterId the meter id of the meter to remove.
+ */
+ void withdraw(MeterRequest meter, MeterId meterId);
+
+ /**
+ * Fetch the meter by the meter id.
+ *
+ * @param id a meter id
+ * @return a meter
+ */
+ Meter getMeter(MeterId id);
+
+ /**
+ * Fetches all the meters.
+ *
+ * @return a collection of meters
+ */
+ Collection<Meter> getAllMeters();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterState.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterState.java
new file mode 100644
index 00000000..3b936099
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterState.java
@@ -0,0 +1,43 @@
+/*
+ * 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.meter;
+
+/**
+ * Represents the state of the meter as seen by the store.
+ */
+public enum MeterState {
+
+ /**
+ * The meter is in the process of being added.
+ */
+ PENDING_ADD,
+
+ /**
+ * THe meter has been added.
+ */
+ ADDED,
+
+ /**
+ * The meter is in the process of being removed.
+ */
+ PENDING_REMOVE,
+
+ /**
+ * The meter has been removed.
+ */
+ REMOVED,
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java
new file mode 100644
index 00000000..5112a4a3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStore.java
@@ -0,0 +1,90 @@
+/*
+ * 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.meter;
+
+import org.onosproject.store.Store;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Entity that stores and distributed meter objects.
+ */
+public interface MeterStore extends Store<MeterEvent, MeterStoreDelegate> {
+
+ /**
+ * Adds a meter to the store.
+ *
+ * @param meter a meter
+ * @return a future indicating the result of the store operation
+ */
+ CompletableFuture<MeterStoreResult> storeMeter(Meter meter);
+
+ /**
+ * Deletes a meter from the store.
+ *
+ * @param meter a meter
+ * @return a future indicating the result of the store operation
+ */
+ CompletableFuture<MeterStoreResult> deleteMeter(Meter meter);
+
+ /**
+ * Updates a meter whose meter id is the same as the passed meter.
+ *
+ * @param meter a new meter
+ * @return a future indicating the result of the store operation
+ */
+ CompletableFuture<MeterStoreResult> updateMeter(Meter meter);
+
+ /**
+ * Updates a given meter's state with the provided state.
+ *
+ * @param meter a meter
+ */
+ void updateMeterState(Meter meter);
+
+ /**
+ * Obtains a meter matching the given meter id.
+ *
+ * @param meterId a meter id
+ * @return a meter
+ */
+ Meter getMeter(MeterId meterId);
+
+ /**
+ * Returns all meters stored in the store.
+ *
+ * @return a collection of meters
+ */
+ Collection<Meter> getAllMeters();
+
+ /**
+ * Update the store by deleting the failed meter.
+ * Notifies the delegate that the meter failed to allow it
+ * to nofity the app.
+ *
+ * @param op a failed meter operation
+ * @param reason a failure reason
+ */
+ void failedMeter(MeterOperation op, MeterFailReason reason);
+
+ /**
+ * Delete this meter immediately.
+ * @param m a meter
+ */
+ void deleteMeterNow(Meter m);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreDelegate.java
new file mode 100644
index 00000000..9bfeb42f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.meter;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Meter store delegate abstraction.
+ */
+public interface MeterStoreDelegate extends StoreDelegate<MeterEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreResult.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreResult.java
new file mode 100644
index 00000000..7a26746f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/MeterStoreResult.java
@@ -0,0 +1,66 @@
+/*
+ * 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.meter;
+
+import java.util.Optional;
+
+/**
+ * An entity used to indicate whether the store operation passed.
+ */
+public final class MeterStoreResult {
+
+
+ private final Type type;
+ private final Optional<MeterFailReason> reason;
+
+ public enum Type {
+ SUCCESS,
+ FAIL
+ }
+
+ private MeterStoreResult(Type type, MeterFailReason reason) {
+ this.type = type;
+ this.reason = Optional.ofNullable(reason);
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ public Optional<MeterFailReason> reason() {
+ return reason;
+ }
+
+ /**
+ * A successful store opertion.
+ *
+ * @return a meter store result
+ */
+ public static MeterStoreResult success() {
+ return new MeterStoreResult(Type.SUCCESS, null);
+ }
+
+ /**
+ * A failed store operation.
+ *
+ * @param reason a failure reason
+ * @return a meter store result
+ */
+ public static MeterStoreResult fail(MeterFailReason reason) {
+ return new MeterStoreResult(Type.FAIL, reason);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/package-info.java
new file mode 100644
index 00000000..258634da
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/meter/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.
+ */
+
+/**
+ * Flow meter model and related services.
+ */
+package org.onosproject.net.meter; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAdminService.java
new file mode 100644
index 00000000..e94ee452
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAdminService.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Service for administering resource service behavior.
+ */
+@Beta
+public interface ResourceAdminService {
+ /**
+ * Register resources as the children of the parent resource path.
+ *
+ * @param parent parent resource path under which the resource are registered
+ * @param children resources to be registered as the children of the parent
+ * @param <T> type of resources
+ * @return true if registration is successfully done, false otherwise. Registration
+ * succeeds when each resource is not registered or unallocated.
+ */
+ default <T> boolean registerResources(ResourcePath parent, T... children) {
+ return registerResources(parent, Arrays.asList(children));
+ }
+
+ /**
+ * Register resources as the children of the parent resource path.
+ *
+ * @param parent parent resource path under which the resource are registered
+ * @param children resources to be registered as the children of the parent
+ * @param <T> type of resources
+ * @return true if registration is successfully done, false otherwise. Registration
+ * succeeds when each resource is not registered or unallocated.
+ */
+ <T> boolean registerResources(ResourcePath parent, List<T> children);
+
+ /**
+ * Unregister resources as the children of the parent resource path.
+ *
+ * @param parent parent resource path under which the resource are unregistered
+ * @param children resources to be unregistered as the children of the parent
+ * @param <T> type of resources
+ * @return true if unregistration is successfully done, false otherwise. Unregistration
+ * succeeds when each resource is not registered or unallocated.
+ */
+ default <T> boolean unregisterResources(ResourcePath parent, T... children) {
+ return unregisterResources(parent, Arrays.asList(children));
+ }
+
+ /**
+ * Unregister resources as the children of the parent resource path.
+ *
+ * @param parent parent resource path under which the resource are unregistered
+ * @param children resources to be unregistered as the children of the parent
+ * @param <T> type of resources
+ * @return true if unregistration is successfully done, false otherwise. Unregistration
+ * succeeds when each resource is not registered or unallocated.
+ */
+ <T> boolean unregisterResources(ResourcePath parent, List<T> children);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAllocation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAllocation.java
new file mode 100644
index 00000000..e6980267
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceAllocation.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents allocation of resource which is identified by the specifier.
+ */
+@Beta
+public class ResourceAllocation {
+
+ private final ResourcePath resource;
+ private final ResourceConsumer consumer;
+
+ /**
+ * Creates an instance with the specified subject, resource and consumer.
+ *
+ * @param resource resource of the subject
+ * @param consumer consumer ot this resource
+ */
+ public ResourceAllocation(ResourcePath resource, ResourceConsumer consumer) {
+ this.resource = checkNotNull(resource);
+ this.consumer = consumer;
+ }
+
+ // for serialization
+ private ResourceAllocation() {
+ this.resource = null;
+ this.consumer = null;
+ }
+
+ /**
+ * Returns the specifier of the resource this allocation uses.
+ *
+ * @return the specifier of the resource this allocation uses
+ */
+ public ResourcePath resource() {
+ return resource;
+ }
+
+ /**
+ * Returns the consumer of this resource.
+ *
+ * @return the consumer of this resource
+ */
+ public ResourceConsumer consumer() {
+ return consumer;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(resource, consumer);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ResourceAllocation)) {
+ return false;
+ }
+ final ResourceAllocation that = (ResourceAllocation) obj;
+ return Objects.equals(this.resource, that.resource)
+ && Objects.equals(this.consumer, that.consumer);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("resource", resource)
+ .add("consumer", consumer)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceConsumer.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceConsumer.java
new file mode 100644
index 00000000..1f67e204
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceConsumer.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Marker interface representing an entity using resource.
+ */
+@Beta
+public interface ResourceConsumer {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java
new file mode 100644
index 00000000..3aa29f6b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An object that is used to locate a resource in a network.
+ * A ResourcePath represents a path that is hierarchical and composed of a sequence
+ * of elementary resources that are not globally identifiable. A ResourcePath can be a globally
+ * unique resource identifier.
+ *
+ * Users of this class must keep the semantics of resources regarding the hierarchical structure.
+ * For example, resource path, Link:1/VLAN ID:100, is valid, but resource path, VLAN ID:100/Link:1
+ * is not valid because a link is not a sub-component of a VLAN ID.
+ */
+@Beta
+public final class ResourcePath {
+
+ private final List<Object> resources;
+
+ public static final ResourcePath ROOT = new ResourcePath(ImmutableList.of());
+
+ public static ResourcePath child(ResourcePath parent, Object child) {
+ ImmutableList<Object> components = ImmutableList.builder()
+ .addAll(parent.components())
+ .add(child)
+ .build();
+ return new ResourcePath(components);
+ }
+
+ /**
+ * Creates an resource path from the specified components.
+ *
+ * @param components components of the path. The order represents hierarchical structure of the resource.
+ */
+ public ResourcePath(Object... components) {
+ this(Arrays.asList(components));
+ }
+
+ /**
+ * Creates an resource path from the specified components.
+ *
+ * @param components components of the path. The order represents hierarchical structure of the resource.
+ */
+ public ResourcePath(List<Object> components) {
+ checkNotNull(components);
+
+ this.resources = ImmutableList.copyOf(components);
+ }
+
+ // for serialization
+ private ResourcePath() {
+ this.resources = null;
+ }
+
+ /**
+ * Returns the components of this resource path.
+ *
+ * @return the components of this resource path
+ */
+ public List<Object> components() {
+ return resources;
+ }
+
+ /**
+ * Returns the parent resource path of this instance.
+ * E.g. if this path is Link:1/VLAN ID:100, the return value is the resource path for Link:1.
+ *
+ * @return the parent resource path of this instance.
+ * If there is no parent, empty instance will be returned.
+ */
+ public Optional<ResourcePath> parent() {
+ if (!isRoot()) {
+ return Optional.of(new ResourcePath(resources.subList(0, resources.size() - 1)));
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Returns true if the path represents root.
+ *
+ * @return true if the path represents root, false otherwise.
+ */
+ public boolean isRoot() {
+ return resources.size() == 0;
+ }
+
+ /**
+ * Returns the last component of this instance.
+ *
+ * @return the last component of this instance.
+ * The return value is equal to the last object of {@code components()}.
+ */
+ public Object lastComponent() {
+ int last = resources.size() - 1;
+ return resources.get(last);
+ }
+
+ @Override
+ public int hashCode() {
+ return resources.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ResourcePath)) {
+ return false;
+ }
+ final ResourcePath that = (ResourcePath) obj;
+ return Objects.equals(this.resources, that.resources);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("resources", resources)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceService.java
new file mode 100644
index 00000000..618042a3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceService.java
@@ -0,0 +1,155 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Service for allocating/releasing resource(s) and retrieving allocation(s) and availability.
+ */
+@Beta
+public interface ResourceService {
+ /**
+ * Allocates the specified resource to the specified user.
+ *
+ * @param consumer resource user which the resource is allocated to
+ * @param resource resource to be allocated
+ * @return allocation information enclosed by Optional. If the allocation fails, the return value is empty
+ */
+ default Optional<ResourceAllocation> allocate(ResourceConsumer consumer, ResourcePath resource) {
+ checkNotNull(consumer);
+ checkNotNull(resource);
+
+ List<ResourceAllocation> allocations = allocate(consumer, ImmutableList.of(resource));
+ if (allocations.isEmpty()) {
+ return Optional.empty();
+ }
+
+ assert allocations.size() == 1;
+
+ ResourceAllocation allocation = allocations.get(0);
+
+ assert allocation.resource().equals(resource);
+
+ // cast is ensured by the assertions above
+ return Optional.of(allocation);
+ }
+
+ /**
+ * Transactionally allocates the specified resources to the specified user.
+ * All allocations are made when this method succeeds, or no allocation is made when this method fails.
+ *
+ * @param consumer resource user which the resources are allocated to
+ * @param resources resources to be allocated
+ * @return non-empty list of allocation information if succeeded, otherwise empty list
+ */
+ List<ResourceAllocation> allocate(ResourceConsumer consumer, List<ResourcePath> resources);
+
+ /**
+ * Transactionally allocates the specified resources to the specified user.
+ * All allocations are made when this method succeeds, or no allocation is made when this method fails.
+ *
+ * @param consumer resource user which the resources are allocated to
+ * @param resources resources to be allocated
+ * @return non-empty list of allocation information if succeeded, otherwise empty list
+ */
+ default List<ResourceAllocation> allocate(ResourceConsumer consumer, ResourcePath... resources) {
+ checkNotNull(consumer);
+ checkNotNull(resources);
+
+ return allocate(consumer, Arrays.asList(resources));
+ }
+
+ /**
+ * Releases the specified resource allocation.
+ *
+ * @param allocation resource allocation to be released
+ * @return true if succeeded, otherwise false
+ */
+ default boolean release(ResourceAllocation allocation) {
+ checkNotNull(allocation);
+
+ return release(ImmutableList.of(allocation));
+ }
+
+ /**
+ * Transactionally releases the specified resource allocations.
+ * All allocations are released when this method succeeded, or no allocation is released when this method fails.
+ *
+ * @param allocations resource allocations to be released
+ * @return true if succeeded, otherwise false
+ */
+ boolean release(List<ResourceAllocation> allocations);
+
+ /**
+ * Transactionally releases the specified resource allocations.
+ * All allocations are released when this method succeeded, or no allocation is released when this method fails.
+ *
+ * @param allocations resource allocations to be released
+ * @return true if succeeded, otherwise false
+ */
+ default boolean release(ResourceAllocation... allocations) {
+ checkNotNull(allocations);
+
+ return release(ImmutableList.copyOf(allocations));
+ }
+
+ /**
+ * Transactionally releases the resources allocated to the specified consumer.
+ * All allocations are released when this method succeeded, or no allocation is released when this method fails.
+ *
+ * @param consumer consumer whose allocated resources are to be released
+ * @return true if succeeded, otherwise false
+ */
+ boolean release(ResourceConsumer consumer);
+
+ /**
+ * Returns allocated resources being as children of the specified parent and being the specified resource type.
+ *
+ * @param parent parent resource path
+ * @param cls class to specify a type of resource
+ * @param <T> type of the resource
+ * @return non-empty collection of resource allocations if resources are allocated with the subject and type,
+ * empty collection if no resource is allocated with the subject and type
+ */
+ <T> Collection<ResourceAllocation> getResourceAllocations(ResourcePath parent, Class<T> cls);
+
+ /**
+ * Returns resources allocated to the specified consumer.
+ *
+ * @param consumer consumer whose allocated resources are to be returned
+ * @return resources allocated to the consumer
+ */
+ Collection<ResourceAllocation> getResourceAllocations(ResourceConsumer consumer);
+
+ /**
+ * Returns the availability of the specified resource.
+ *
+ * @param resource resource to check the availability
+ * @return true if available, otherwise false
+ */
+ boolean isAvailable(ResourcePath resource);
+
+ // TODO: listener and event mechanism need to be considered
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceStore.java
new file mode 100644
index 00000000..fc2eba70
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/ResourceStore.java
@@ -0,0 +1,90 @@
+package org.onosproject.net.newresource;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Service for storing resource and consumer information.
+ */
+@Beta
+public interface ResourceStore {
+
+ /**
+ * Registers the resources in transactional way.
+ * Resource registration must be done before resource allocation. The state after completion
+ * of this method is all the resources are registered, or none of the given resources is registered.
+ * The whole registration fails when any one of the resource can't be registered.
+ *
+ * @param resources resources to be registered
+ * @return true if the registration succeeds, false otherwise
+ */
+ boolean register(List<ResourcePath> resources);
+
+ /**
+ * Unregisters the resources in transactional way.
+ * The state after completion of this method is all the resources are unregistered,
+ * or none of the given resources is unregistered. The whole unregistration fails when any one of the
+ * resource can't be unregistered.
+ *
+ * @param resources resources to be unregistered
+ * @return true if the registration succeeds, false otherwise
+ */
+ boolean unregister(List<ResourcePath> resources);
+
+ /**
+ * Allocates the specified resources to the specified consumer in transactional way.
+ * The state after completion of this method is all the resources are allocated to the consumer,
+ * or no resource is allocated to the consumer. The whole allocation fails when any one of
+ * the resource can't be allocated.
+ *
+ * @param resources resources to be allocated
+ * @param consumer resource consumer which the resources are allocated to
+ * @return true if the allocation succeeds, false otherwise.
+ */
+ boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer);
+
+ /**
+ * Releases the specified resources allocated to the specified corresponding consumers
+ * in transactional way. The state after completion of this method is all the resources
+ * are released from the consumer, or no resource is released. The whole release fails
+ * when any one of the resource can't be released. The size of the list of resources and
+ * that of consumers must be equal. The resource and consumer with the same position from
+ * the head of each list correspond to each other.
+ *
+ * @param resources resources to be released
+ * @param consumers resource consumers to whom the resource allocated to
+ * @return true if succeeds, otherwise false
+ */
+ boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers);
+
+ /**
+ * Returns the resource consumer to whom the specified resource is allocated.
+ *
+ * @param resource resource whose allocated consumer to be returned
+ * @return resource consumer who are allocated the resource
+ */
+ Optional<ResourceConsumer> getConsumer(ResourcePath resource);
+
+ /**
+ * Returns a collection of the resources allocated to the specified consumer.
+ *
+ * @param consumer resource consumer whose allocated resource are searched for
+ * @return a collection of the resources allocated to the specified consumer
+ */
+ Collection<ResourcePath> getResources(ResourceConsumer consumer);
+
+ /**
+ * Returns a collection of the resources which are children of the specified parent and
+ * whose type is the specified class.
+ *
+ * @param parent parent of the resources to be returned
+ * @param cls class instance of the children
+ * @param <T> type of the resource
+ * @return a collection of the resources which belongs to the specified subject and
+ * whose type is the specified class.
+ */
+ <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/package-info.java
new file mode 100644
index 00000000..52bb8588
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/newresource/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Generic network resource model and services for resource allocation and
+ * resource tracking.
+ */
+package org.onosproject.net.newresource; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/package-info.java
new file mode 100644
index 00000000..34b4de21
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/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.
+ */
+
+/**
+ * Network model entities &amp; service API definitions.
+ */
+package org.onosproject.net;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultInboundPacket.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultInboundPacket.java
new file mode 100644
index 00000000..96f872f5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultInboundPacket.java
@@ -0,0 +1,91 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.ConnectPoint;
+import org.onlab.packet.Ethernet;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of an immutable inbound packet.
+ */
+public final class DefaultInboundPacket implements InboundPacket {
+
+ private final ConnectPoint receivedFrom;
+ private final Ethernet parsed;
+ private final ByteBuffer unparsed;
+
+ /**
+ * Creates an immutable inbound packet.
+ *
+ * @param receivedFrom connection point where received
+ * @param parsed parsed ethernet frame
+ * @param unparsed unparsed raw bytes
+ */
+ public DefaultInboundPacket(ConnectPoint receivedFrom, Ethernet parsed,
+ ByteBuffer unparsed) {
+ this.receivedFrom = receivedFrom;
+ this.parsed = parsed;
+ this.unparsed = unparsed;
+ }
+
+ @Override
+ public ConnectPoint receivedFrom() {
+ return receivedFrom;
+ }
+
+ @Override
+ public Ethernet parsed() {
+ return parsed;
+ }
+
+ @Override
+ public ByteBuffer unparsed() {
+ // FIXME: figure out immutability here
+ return unparsed;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(receivedFrom, parsed, unparsed);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof InboundPacket) {
+ final DefaultInboundPacket other = (DefaultInboundPacket) obj;
+ return Objects.equals(this.receivedFrom, other.receivedFrom) &&
+ Objects.equals(this.parsed, other.parsed) &&
+ Objects.equals(this.unparsed, other.unparsed);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("receivedFrom", receivedFrom)
+ .add("parsed", parsed)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java
new file mode 100644
index 00000000..5bd4c986
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java
@@ -0,0 +1,89 @@
+/*
+ * 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Default implementation of an immutable outbound packet.
+ */
+public final class DefaultOutboundPacket implements OutboundPacket {
+ private final DeviceId sendThrough;
+ private final TrafficTreatment treatment;
+ private final ByteBuffer data;
+
+ /**
+ * Creates an immutable outbound packet.
+ *
+ * @param sendThrough identifier through which to send the packet
+ * @param treatment list of packet treatments
+ * @param data raw packet data
+ */
+ public DefaultOutboundPacket(DeviceId sendThrough,
+ TrafficTreatment treatment, ByteBuffer data) {
+ this.sendThrough = sendThrough;
+ this.treatment = treatment;
+ this.data = data;
+ }
+
+ @Override
+ public DeviceId sendThrough() {
+ return sendThrough;
+ }
+
+ @Override
+ public TrafficTreatment treatment() {
+ return treatment;
+ }
+
+ @Override
+ public ByteBuffer data() {
+ // FIXME: figure out immutability here
+ return data;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(sendThrough, treatment, data);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OutboundPacket) {
+ final DefaultOutboundPacket other = (DefaultOutboundPacket) obj;
+ return Objects.equals(this.sendThrough, other.sendThrough) &&
+ Objects.equals(this.treatment, other.treatment) &&
+ Objects.equals(this.data, other.data);
+ }
+ return false;
+ }
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("sendThrough", sendThrough)
+ .add("treatment", treatment)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketContext.java
new file mode 100644
index 00000000..166269f9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketContext.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment.Builder;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.onosproject.security.AppPermission.Type.*;
+
+/**
+ * Default implementation of a packet context.
+ */
+public abstract class DefaultPacketContext implements PacketContext {
+
+ private final long time;
+ private final InboundPacket inPkt;
+ private final OutboundPacket outPkt;
+ private final TrafficTreatment.Builder builder;
+
+ private final AtomicBoolean block;
+
+ /**
+ * Creates a new packet context.
+ *
+ * @param time creation time
+ * @param inPkt inbound packet
+ * @param outPkt outbound packet
+ * @param block whether the context is blocked or not
+ */
+ protected DefaultPacketContext(long time, InboundPacket inPkt,
+ OutboundPacket outPkt, boolean block) {
+ super();
+ this.time = time;
+ this.inPkt = inPkt;
+ this.outPkt = outPkt;
+ this.block = new AtomicBoolean(block);
+ this.builder = DefaultTrafficTreatment.builder();
+ }
+
+ @Override
+ public long time() {
+ checkPermission(PACKET_READ);
+ return time;
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ checkPermission(PACKET_READ);
+ return inPkt;
+ }
+
+ @Override
+ public OutboundPacket outPacket() {
+ checkPermission(PACKET_READ);
+ return outPkt;
+ }
+
+ @Override
+ public Builder treatmentBuilder() {
+ checkPermission(PACKET_READ);
+ return builder;
+ }
+
+ @Override
+ public abstract void send();
+
+ @Override
+ public boolean block() {
+ checkPermission(PACKET_WRITE);
+ return this.block.getAndSet(true);
+ }
+
+ @Override
+ public boolean isHandled() {
+ checkPermission(PACKET_READ);
+ return this.block.get();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java
new file mode 100644
index 00000000..ce2eb118
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/DefaultPacketRequest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.packet;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficSelector;
+
+import java.util.Objects;
+
+/**
+ * Default implementation of a packet request.
+ */
+public final class DefaultPacketRequest implements PacketRequest {
+ private final TrafficSelector selector;
+ private final PacketPriority priority;
+ private final ApplicationId appId;
+
+ /**
+ * Creates a new packet request.
+ *
+ * @param selector traffic selector
+ * @param priority intercept priority
+ * @param appId application id
+ */
+ public DefaultPacketRequest(TrafficSelector selector, PacketPriority priority,
+ ApplicationId appId) {
+ this.selector = selector;
+ this.priority = priority;
+ this.appId = appId;
+ }
+
+ public TrafficSelector selector() {
+ return selector;
+ }
+
+ public PacketPriority priority() {
+ return priority;
+ }
+
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(selector, priority, appId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final DefaultPacketRequest other = (DefaultPacketRequest) obj;
+ return Objects.equals(this.selector, other.selector)
+ && Objects.equals(this.priority, other.priority)
+ && Objects.equals(this.appId, other.appId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this.getClass())
+ .add("selector", selector)
+ .add("priority", priority)
+ .add("appId", appId).toString();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/InboundPacket.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/InboundPacket.java
new file mode 100644
index 00000000..3fd58149
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/InboundPacket.java
@@ -0,0 +1,50 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.ConnectPoint;
+import org.onlab.packet.Ethernet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents a data packet intercepted from an infrastructure device.
+ */
+public interface InboundPacket {
+
+ /**
+ * Returns the device and port from where the packet was received.
+ *
+ * @return connection point where received
+ */
+ ConnectPoint receivedFrom();
+
+ /**
+ * Returns the parsed form of the packet.
+ *
+ * @return parsed Ethernet frame; null if the packet is not an Ethernet
+ * frame or one for which there is no parser
+ */
+ Ethernet parsed();
+
+ /**
+ * Unparsed packet data.
+ *
+ * @return raw packet bytes
+ */
+ ByteBuffer unparsed();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java
new file mode 100644
index 00000000..9e9329fa
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java
@@ -0,0 +1,51 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents an outbound data packet that is to be emitted to network via
+ * an infrastructure device.
+ */
+public interface OutboundPacket {
+
+ /**
+ * Returns the identity of a device through which this packet should be
+ * sent.
+ *
+ * @return device identity
+ */
+ DeviceId sendThrough();
+
+ /**
+ * Returns how the outbound packet should be treated.
+ *
+ * @return output treatment
+ */
+ TrafficTreatment treatment();
+
+ /**
+ * Returns immutable view of the raw data to be sent.
+ *
+ * @return data to emit
+ */
+ ByteBuffer data();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketContext.java
new file mode 100644
index 00000000..004adc87
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketContext.java
@@ -0,0 +1,73 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.flow.TrafficTreatment;
+
+/**
+ * Represents context for processing an inbound packet, and (optionally)
+ * emitting a corresponding outbound packet.
+ */
+public interface PacketContext {
+
+ /**
+ * Returns the time when the packet was received.
+ *
+ * @return the time in millis since start of epoch
+ */
+ long time();
+
+ /**
+ * Returns the inbound packet being processed.
+ *
+ * @return inbound packet
+ */
+ InboundPacket inPacket();
+
+ /**
+ * Returns the view of the outbound packet.
+ *
+ * @return outbound packet
+ */
+ OutboundPacket outPacket();
+
+ /**
+ * Returns a builder for constructing traffic treatment.
+ *
+ * @return traffic treatment builder
+ */
+ TrafficTreatment.Builder treatmentBuilder();
+
+ /**
+ * Triggers the outbound packet to be sent.
+ */
+ void send();
+
+ /**
+ * Blocks the outbound packet from being sent from this point onward.
+ *
+ * @return whether the outbound packet is blocked.
+ */
+ boolean block();
+
+ /**
+ * Indicates whether the outbound packet is handled, i.e. sent or blocked.
+ *
+ * @return true uf the packed is handled
+ */
+ boolean isHandled();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketEvent.java
new file mode 100644
index 00000000..7b0a5ed7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketEvent.java
@@ -0,0 +1,56 @@
+/*
+ * 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.packet;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes a packet event.
+ */
+public class PacketEvent extends AbstractEvent<PacketEvent.Type, OutboundPacket> {
+
+ /**
+ * Type of packet events.
+ */
+ public enum Type {
+ /**
+ * Signifies that the packet should be emitted out a local port.
+ */
+ EMIT
+ }
+
+ /**
+ * Creates an event of the given type for the specified packet.
+ *
+ * @param type the type of the event
+ * @param packet the packet the event is about
+ */
+ public PacketEvent(Type type, OutboundPacket packet) {
+ super(type, packet);
+ }
+
+ /**
+ * Creates an event of the given type for the specified packet at the given
+ * time.
+ *
+ * @param type the type of the event
+ * @param packet the packet the event is about
+ * @param time the time of the event
+ */
+ public PacketEvent(Type type, OutboundPacket packet, long time) {
+ super(type, packet, time);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketPriority.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketPriority.java
new file mode 100644
index 00000000..68c0a838
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketPriority.java
@@ -0,0 +1,53 @@
+/*
+ * 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.packet;
+
+/**
+ * Priorities available to applications for requests for packets from the data
+ * plane.
+ */
+public enum PacketPriority {
+ /**
+ * High priority for control traffic. This will result in all traffic
+ * matching the selector being sent to the controller.
+ */
+ CONTROL(40000),
+
+ /**
+ * Low priority for reactive applications. Packets are only sent to the
+ * controller if they fail to match any of the rules installed in the switch.
+ */
+ REACTIVE(5);
+
+ private final int priorityValue;
+
+ private PacketPriority(int priorityValue) {
+ this.priorityValue = priorityValue;
+ }
+
+ /**
+ * Returns the integer value of the priority level.
+ *
+ * @return priority value
+ */
+ public int priorityValue() {
+ return priorityValue;
+ }
+
+ public String toString() {
+ return String.valueOf(priorityValue);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProcessor.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProcessor.java
new file mode 100644
index 00000000..98886775
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProcessor.java
@@ -0,0 +1,83 @@
+/*
+ * 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.packet;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Abstraction of an inbound packet processor.
+ */
+public interface PacketProcessor {
+
+ static final int ADVISOR_MAX = Integer.MAX_VALUE / 3;
+ static final int DIRECTOR_MAX = (Integer.MAX_VALUE / 3) * 2;
+ static final int OBSERVER_MAX = Integer.MAX_VALUE;
+
+ /**
+ * Returns a priority in the ADVISOR range, where processors can take early action and
+ * influence the packet context. However, they cannot handle the packet (i.e. call send() or block()).
+ * The valid range is from 1 to ADVISOR_MAX.
+ * Processors in this range get to see the packet first.
+ *
+ * @param priority priority within ADVISOR range
+ * @return overall priority
+ */
+ static int advisor(int priority) {
+ int overallPriority = priority + 1;
+ checkArgument(overallPriority > 0 && overallPriority <= ADVISOR_MAX,
+ "Priority not within ADVISOR range");
+ return overallPriority;
+ }
+
+ /**
+ * Returns a priority in the DIRECTOR range, where processors can handle the packet.
+ * The valid range is from ADVISOR_MAX+1 to DIRECTOR_MAX.
+ * Processors in this range get to see the packet second, after ADVISORS.
+ *
+ * @param priority priority within the DIRECTOR range
+ * @return overall priority
+ */
+ static int director(int priority) {
+ int overallPriority = ADVISOR_MAX + priority + 1;
+ checkArgument(overallPriority > ADVISOR_MAX && overallPriority <= DIRECTOR_MAX,
+ "Priority not within DIRECTOR range");
+ return overallPriority;
+ }
+
+ /**
+ * Returns a priority in the OBSERVER range, where processors cannot take any action,
+ * but can observe what action has been taken until then.
+ * The valid range is from DIRECTOR_MAX+1 to OBSERVER_MAX.
+ * Processors in this range get to see the packet last, after ADVISORS and DIRECTORS.
+ *
+ * @param priority priority within the OBSERVER range
+ * @return overall priority
+ */
+ static int observer(int priority) {
+ int overallPriority = DIRECTOR_MAX + priority + 1;
+ checkArgument(overallPriority > DIRECTOR_MAX && overallPriority <= OBSERVER_MAX,
+ "Priority not within OBSERVER range");
+ return overallPriority;
+ }
+
+ /**
+ * Processes the inbound packet as specified in the given context.
+ *
+ * @param context packet processing context
+ */
+ void process(PacketContext context);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProvider.java
new file mode 100644
index 00000000..8d55f0d1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProvider.java
@@ -0,0 +1,32 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of a packet provider capable of emitting packets.
+ */
+public interface PacketProvider extends Provider {
+
+ /**
+ * Emits the specified outbound packet onto the network.
+ *
+ * @param packet outbound packet
+ */
+ void emit(OutboundPacket packet);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderRegistry.java
new file mode 100644
index 00000000..6e73253e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction of an infrastructure packet provider registry.
+ */
+public interface PacketProviderRegistry
+extends ProviderRegistry<PacketProvider, PacketProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderService.java
new file mode 100644
index 00000000..1aaee65f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketProviderService.java
@@ -0,0 +1,33 @@
+/*
+ * 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.packet;
+
+import org.onosproject.net.provider.ProviderService;
+
+/**
+ * Entity capable of processing inbound packets.
+ */
+public interface PacketProviderService extends ProviderService<PacketProvider> {
+
+ /**
+ * Submits inbound packet context for processing. This processing will be
+ * done synchronously, i.e. run-to-completion.
+ *
+ * @param context inbound packet context
+ */
+ void processPacket(PacketContext context);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java
new file mode 100644
index 00000000..dc09219a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketRequest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.packet;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficSelector;
+
+/**
+ * Represents a packet request made to devices.
+ */
+public interface PacketRequest {
+
+ /**
+ * Obtain the traffic selector.
+ *
+ * @return a traffic selector
+ */
+ TrafficSelector selector();
+
+ /**
+ * Obtain the priority.
+ *
+ * @return a PacketPriority
+ */
+ PacketPriority priority();
+
+ /**
+ * Obtain the application id.
+ *
+ * @return an application id
+ */
+ ApplicationId appId();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketService.java
new file mode 100644
index 00000000..06c416ec
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketService.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficSelector;
+
+/**
+ * Service for intercepting data plane packets and for emitting synthetic
+ * outbound packets.
+ */
+public interface PacketService {
+
+ // TODO: ponder better ordering scheme that does not require absolute numbers
+
+ /**
+ * Adds the specified processor to the list of packet processors.
+ * It will be added into the list in the order of priority. The higher
+ * numbers will be processing the packets after the lower numbers.
+ *
+ * @param processor processor to be added
+ * @param priority priority in the reverse natural order
+ * @throws java.lang.IllegalArgumentException if a processor with the
+ * given priority already exists
+ */
+ void addProcessor(PacketProcessor processor, int priority);
+
+ // TODO allow processors to register for particular types of packets
+
+ /**
+ * Removes the specified processor from the processing pipeline.
+ *
+ * @param processor packet processor
+ */
+ void removeProcessor(PacketProcessor processor);
+
+ /**
+ * Requests that packets matching the given selector are punted from the
+ * dataplane to the controller.
+ *
+ * @param selector the traffic selector used to match packets
+ * @param priority the priority of the rule
+ * @param appId the application ID of the requester
+ */
+ void requestPackets(TrafficSelector selector, PacketPriority priority,
+ ApplicationId appId);
+
+ /**
+ * Cancels previous packet requests for packets matching the given
+ * selector to be punted from the dataplane to the controller.
+ *
+ * @param selector the traffic selector used to match packets
+ * @param priority the priority of the rule
+ * @param appId the application ID of the requester
+ */
+ void cancelPackets(TrafficSelector selector, PacketPriority priority,
+ ApplicationId appId);
+
+ /**
+ * Emits the specified outbound packet onto the network.
+ *
+ * @param packet outbound packet
+ */
+ void emit(OutboundPacket packet);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStore.java
new file mode 100644
index 00000000..ff45cc0c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStore.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Manages routing of outbound packets.
+ */
+public interface PacketStore extends Store<PacketEvent, PacketStoreDelegate> {
+
+ /**
+ * Decides which instance should emit the packet and forwards the packet to
+ * that instance. The relevant PacketManager is notified via the
+ * PacketStoreDelegate that it should emit the packet.
+ *
+ * @param packet the packet to emit
+ */
+ void emit(OutboundPacket packet);
+
+ /**
+ * Requests intercept of packets that match the given selector.
+ *
+ * @param request a packet request
+ * @return true if the first time the given selector was requested
+ */
+ boolean requestPackets(PacketRequest request);
+
+ /**
+ * Cancels intercept of packets that match the given selector.
+ *
+ * @param request a packet request
+ * @return true if there is no other application requesting the given selector
+ */
+ boolean cancelPackets(PacketRequest request);
+
+ /**
+ * Obtains all existing requests in the system.
+ *
+ * @return a set of packet requests
+ */
+ Set<PacketRequest> existingRequests();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStoreDelegate.java
new file mode 100644
index 00000000..bf5c3cc0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/PacketStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.packet;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Packet store delegate abstraction.
+ */
+public interface PacketStoreDelegate extends StoreDelegate<PacketEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/package-info.java
new file mode 100644
index 00000000..0b9ea377
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/packet/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.
+ */
+
+/**
+ * Mechanism for processing inbound packets intercepted from the data plane and
+ * for emitting outbound packets onto the data plane.
+ */
+package org.onosproject.net.packet;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractListenerProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractListenerProviderRegistry.java
new file mode 100644
index 00000000..ff67c6da
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractListenerProviderRegistry.java
@@ -0,0 +1,66 @@
+/*
+ * 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.provider;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventListener;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.event.ListenerService;
+
+/**
+ * Basis for components which need to export listener mechanism.
+ */
+@Component(componentAbstract = true)
+public abstract class AbstractListenerProviderRegistry<E extends Event, L extends EventListener<E>,
+ P extends Provider, S extends ProviderService<P>>
+ extends AbstractProviderRegistry<P, S> implements ListenerService<E, L> {
+
+ // If only Java supported mixins...
+
+ protected final ListenerRegistry<E, L> listenerRegistry = new ListenerRegistry<>();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ @Override
+ public void addListener(L listener) {
+ listenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(L listener) {
+ listenerRegistry.removeListener(listener);
+ }
+
+
+ /**
+ * Safely posts the specified event to the local event dispatcher.
+ * If there is no event dispatcher or if the event is null, this method
+ * is a noop.
+ *
+ * @param event event to be posted; may be null
+ */
+ protected void post(E event) {
+ if (event != null && eventDispatcher != null) {
+ eventDispatcher.post(event);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProvider.java
new file mode 100644
index 00000000..a4926dd3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.provider;
+
+/**
+ * Base provider implementation.
+ */
+public abstract class AbstractProvider implements Provider {
+
+ private final ProviderId providerId;
+
+ /**
+ * Creates a provider with the supplier identifier.
+ *
+ * @param id provider id
+ */
+ protected AbstractProvider(ProviderId id) {
+ this.providerId = id;
+ }
+
+ @Override
+ public ProviderId id() {
+ return providerId;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java
new file mode 100644
index 00000000..bcf5fae0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java
@@ -0,0 +1,111 @@
+/*
+ * 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.provider;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of provider registry.
+ *
+ * @param <P> type of the information provider
+ * @param <S> type of the provider service
+ */
+public abstract class AbstractProviderRegistry<P extends Provider, S extends ProviderService<P>>
+ implements ProviderRegistry<P, S> {
+
+ private final Map<ProviderId, P> providers = new HashMap<>();
+ private final Map<ProviderId, S> services = new HashMap<>();
+ private final Map<String, P> providersByScheme = new HashMap<>();
+
+ /**
+ * Creates a new provider service bound to the specified provider.
+ *
+ * @param provider provider
+ * @return provider service
+ */
+ protected abstract S createProviderService(P provider);
+
+ @Override
+ public synchronized S register(P provider) {
+ checkNotNull(provider, "Provider cannot be null");
+ checkState(!services.containsKey(provider.id()), "Provider %s already registered", provider.id());
+
+ // If the provider is a primary one, check for a conflict.
+ ProviderId pid = provider.id();
+ checkState(pid.isAncillary() || !providersByScheme.containsKey(pid.scheme()),
+ "A primary provider with id %s is already registered",
+ providersByScheme.get(pid.scheme()));
+
+ S service = createProviderService(provider);
+ services.put(provider.id(), service);
+ providers.put(provider.id(), provider);
+
+ // Register the provider by URI scheme only if it is not ancillary.
+ if (!pid.isAncillary()) {
+ providersByScheme.put(pid.scheme(), provider);
+ }
+
+ return service;
+ }
+
+ @Override
+ public synchronized void unregister(P provider) {
+ checkNotNull(provider, "Provider cannot be null");
+ S service = services.get(provider.id());
+ if (service != null && service instanceof AbstractProviderService) {
+ ((AbstractProviderService) service).invalidate();
+ services.remove(provider.id());
+ providers.remove(provider.id());
+ if (!provider.id().isAncillary()) {
+ providersByScheme.remove(provider.id().scheme());
+ }
+ }
+ }
+
+ @Override
+ public synchronized Set<ProviderId> getProviders() {
+ return ImmutableSet.copyOf(services.keySet());
+ }
+
+ /**
+ * Returns the provider registered with the specified provider ID.
+ *
+ * @param providerId provider identifier
+ * @return provider
+ */
+ protected synchronized P getProvider(ProviderId providerId) {
+ return providers.get(providerId);
+ }
+
+ /**
+ * Returns the provider for the specified device ID based on URI scheme.
+ *
+ * @param deviceId device identifier
+ * @return provider bound to the URI scheme
+ */
+ protected synchronized P getProvider(DeviceId deviceId) {
+ return providersByScheme.get(deviceId.uri().getScheme());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderService.java
new file mode 100644
index 00000000..6c926d88
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderService.java
@@ -0,0 +1,61 @@
+/*
+ * 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.provider;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of a provider service, which tracks the provider to
+ * which it is issued and can be invalidated.
+ *
+ * @param <P> type of the information provider
+ */
+public abstract class AbstractProviderService<P extends Provider> implements ProviderService<P> {
+
+ private boolean isValid = true;
+ private final P provider;
+
+ /**
+ * Creates a provider service on behalf of the specified provider.
+ *
+ * @param provider provider to which this service is being issued
+ */
+ protected AbstractProviderService(P provider) {
+ this.provider = provider;
+ }
+
+ /**
+ * Invalidates this provider service.
+ */
+ public void invalidate() {
+ isValid = false;
+ }
+
+ /**
+ * Checks the validity of this provider service.
+ *
+ * @throws java.lang.IllegalStateException if the service is no longer valid
+ */
+ public void checkValidity() {
+ checkState(isValid, "Provider service is no longer valid");
+ }
+
+ @Override
+ public P provider() {
+ return provider;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/Provider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/Provider.java
new file mode 100644
index 00000000..84465ab7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/Provider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.provider;
+
+/**
+ * Abstraction of a provider of information about network environment.
+ */
+public interface Provider {
+
+ /**
+ * Returns the provider identifier.
+ *
+ * @return provider identification
+ */
+ ProviderId id();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderId.java
new file mode 100644
index 00000000..2c959afd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderId.java
@@ -0,0 +1,135 @@
+/*
+ * 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.provider;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * External identity of a {@link org.onosproject.net.provider.Provider} family.
+ * It also carriers two designations of external characteristics, the URI
+ * scheme and primary/ancillary indicator.
+ * <p>
+ * The device URI scheme is used to determine applicability of a provider to
+ * operations on a specific device. The ancillary indicator serves to designate
+ * a provider as a primary or ancillary.
+ * </p>
+ * <p>
+ * A {@link org.onosproject.net.provider.ProviderRegistry} uses this designation
+ * to permit only one primary provider per device URI scheme. Multiple
+ * ancillary providers can register with the same device URI scheme however.
+ * </p>
+ */
+public class ProviderId {
+
+ /**
+ * Represents no provider ID.
+ */
+ public static final ProviderId NONE = new ProviderId("none", "none");
+
+ private final String scheme;
+ private final String id;
+ private final boolean ancillary;
+
+ // For serialization
+ private ProviderId() {
+ scheme = null;
+ id = null;
+ ancillary = false;
+ }
+
+ /**
+ * Creates a new primary provider identifier from the specified string.
+ * The providers are expected to follow the reverse DNS convention, e.g.
+ * {@code org.onosproject.provider.of.device}
+ *
+ * @param scheme device URI scheme to which this provider is bound, e.g. "of", "snmp"
+ * @param id string identifier
+ */
+ public ProviderId(String scheme, String id) {
+ this(scheme, id, false);
+ }
+
+ /**
+ * Creates a new provider identifier from the specified string.
+ * The providers are expected to follow the reverse DNS convention, e.g.
+ * {@code org.onosproject.provider.of.device}
+ *
+ * @param scheme device URI scheme to which this provider is bound, e.g. "of", "snmp"
+ * @param id string identifier
+ * @param ancillary ancillary provider indicator
+ */
+ public ProviderId(String scheme, String id, boolean ancillary) {
+ this.scheme = checkNotNull(scheme, "Scheme cannot be null");
+ this.id = checkNotNull(id, "ID cannot be null");
+ this.ancillary = ancillary;
+ }
+
+ /**
+ * Returns the device URI scheme to which this provider is bound.
+ *
+ * @return device URI scheme
+ */
+ public String scheme() {
+ return scheme;
+ }
+
+ /**
+ * Returns the device URI scheme specific id portion.
+ *
+ * @return id
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Indicates whether this identifier designates an ancillary providers.
+ *
+ * @return true if the provider is ancillary; false if primary
+ */
+ public boolean isAncillary() {
+ return ancillary;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(scheme, id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ProviderId) {
+ final ProviderId other = (ProviderId) obj;
+ return Objects.equals(this.scheme, other.scheme) &&
+ Objects.equals(this.id, other.id) &&
+ this.ancillary == other.ancillary;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("scheme", scheme).add("id", id)
+ .add("ancillary", ancillary).toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderRegistry.java
new file mode 100644
index 00000000..3c2009ad
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderRegistry.java
@@ -0,0 +1,57 @@
+/*
+ * 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.provider;
+
+import java.util.Set;
+
+/**
+ * Registry for tracking information providers with the core.
+ *
+ * @param <P> type of the information provider
+ * @param <S> type of the provider service
+ */
+public interface ProviderRegistry<P extends Provider, S extends ProviderService<P>> {
+
+ /**
+ * Registers the supplied provider with the core.
+ *
+ * @param provider provider to be registered
+ * @return provider service for injecting information into core
+ * @throws java.lang.IllegalArgumentException if the provider is registered already
+ */
+ S register(P provider);
+
+ /**
+ * Unregisters the supplied provider. As a result the previously issued
+ * provider service will be invalidated and any subsequent invocations
+ * of its methods may throw {@link java.lang.IllegalStateException}.
+ * <p>
+ * Unregistering a provider that has not been previously registered results
+ * in a no-op.
+ * </p>
+ *
+ * @param provider provider to be unregistered
+ */
+ void unregister(P provider);
+
+ /**
+ * Returns a set of currently registered provider identities.
+ *
+ * @return set of provider identifiers
+ */
+ Set<ProviderId> getProviders();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderService.java
new file mode 100644
index 00000000..5c469276
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/ProviderService.java
@@ -0,0 +1,33 @@
+/*
+ * 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.provider;
+
+/**
+ * Abstraction of a service through which providers can inject information
+ * about the network environment into the core.
+ *
+ * @param <P> type of the information provider
+ */
+public interface ProviderService<P extends Provider> {
+
+ /**
+ * Returns the provider to which this service has been issued.
+ *
+ * @return provider to which this service has been assigned
+ */
+ P provider();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/package-info.java
new file mode 100644
index 00000000..d279f56a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/provider/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.
+ */
+
+/**
+ * Base abstractions related to network entity providers and their brokers.
+ */
+package org.onosproject.net.provider;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java
new file mode 100644
index 00000000..8ffe17a4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.PacketContext;
+
+/**
+ * Service for processing arp requests on behalf of applications.
+ */
+// TODO: move to the peer host package
+public interface ProxyArpService {
+
+ /**
+ * Returns whether this particular IP address is known to the system.
+ *
+ * @param addr an IP address
+ * @return true if know, false otherwise
+ */
+ boolean isKnown(IpAddress addr);
+
+ /**
+ * Sends a reply for a given request. If the host is not known then the
+ * arp or neighbor solicitation will be flooded at all edge ports.
+ *
+ * @param eth an arp or neighbor solicitation request
+ * @param inPort the port the request was received on
+ */
+ void reply(Ethernet eth, ConnectPoint inPort);
+
+ /**
+ * Forwards an ARP or neighbor solicitation request to its destination.
+ * Floods at the edg the request if the destination is not known.
+ *
+ * @param eth an ethernet frame containing an ARP or neighbor solicitation
+ * request.
+ * @param inPort the port the request was received on
+ */
+ void forward(Ethernet eth, ConnectPoint inPort);
+
+ /**
+ * Handles a arp or neighbor solicitation packet.
+ * Replies to arp or neighbor solicitation requests and forwards request
+ * to the right place.
+ * @param context the packet context to handle
+ * @return true if handled, false otherwise.
+ */
+ boolean handlePacket(PacketContext context);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java
new file mode 100644
index 00000000..b6564212
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java
@@ -0,0 +1,45 @@
+/*
+ * 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.proxyarp;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+
+import java.nio.ByteBuffer;
+
+/**
+ * State distribution mechanism for the proxy ARP service.
+ */
+public interface ProxyArpStore {
+
+ /**
+ * Forwards an ARP or neighbor solicitation request to its destination.
+ * Floods at the edg the request if the destination is not known.
+ *
+ * @param outPort the port the request was received on
+ * @param subject subject host
+ * @param packet an ethernet frame containing an ARP or neighbor
+ * solicitation request
+ */
+ void forward(ConnectPoint outPort, Host subject, ByteBuffer packet);
+
+ /**
+ * Associates the specified delegate with the store.
+ *
+ * @param delegate store delegate
+ */
+ void setDelegate(ProxyArpStoreDelegate delegate);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStoreDelegate.java
new file mode 100644
index 00000000..d0e273c8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStoreDelegate.java
@@ -0,0 +1,35 @@
+/*
+ * 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.proxyarp;
+
+import org.onosproject.net.ConnectPoint;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Proxy ARP store delegate.
+ */
+public interface ProxyArpStoreDelegate {
+
+ /**
+ * Emits ARP or neighbour discovery response packet.
+ *
+ * @param outPort output connection point
+ * @param packet packet to emit
+ */
+ void emitResponse(ConnectPoint outPort, ByteBuffer packet);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/package-info.java
new file mode 100644
index 00000000..6ddd4926
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/proxyarp/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.
+ */
+
+/**
+ * Base abstractions related to the proxy arp service.
+ */
+package org.onosproject.net.proxyarp;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocation.java
new file mode 100644
index 00000000..01b69b2a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocation.java
@@ -0,0 +1,31 @@
+/*
+ * 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.resource;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of allocated resource.
+ */
+@Beta
+public interface ResourceAllocation {
+ /**
+ * Returns the resource type.
+ *
+ * @return the resource type
+ */
+ ResourceType type();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocationException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocationException.java
new file mode 100644
index 00000000..c3d1fcc7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceAllocationException.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Exception thrown for resource allocation errors.
+ */
+@Beta
+public class ResourceAllocationException extends ResourceException {
+ public ResourceAllocationException() {
+ super();
+ }
+
+ public ResourceAllocationException(String message) {
+ super(message);
+ }
+
+ public ResourceAllocationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceException.java
new file mode 100644
index 00000000..31e82d5d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Represents a resource related error.
+ */
+@Beta
+public class ResourceException extends RuntimeException {
+
+ /**
+ * Constructs an exception with no message and no underlying cause.
+ */
+ public ResourceException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message describing the specific nature of the error
+ */
+ public ResourceException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and the underlying cause.
+ *
+ * @param message the message describing the specific nature of the error
+ * @param cause the underlying cause of this error
+ */
+ public ResourceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceId.java
new file mode 100644
index 00000000..722ec4d7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceId.java
@@ -0,0 +1,25 @@
+/*
+ * 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.resource;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Resource identifier.
+ */
+@Beta
+public interface ResourceId {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceRequest.java
new file mode 100644
index 00000000..fb53f120
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceRequest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.resource;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of resource request.
+ */
+@Beta
+public interface ResourceRequest {
+ /**
+ * Returns the resource type.
+ *
+ * @return the resource type
+ */
+ ResourceType type();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceType.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceType.java
new file mode 100644
index 00000000..75832134
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/ResourceType.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Represents types for link resources.
+ */
+@Beta
+public enum ResourceType {
+ /**
+ * Lambda resource type.
+ */
+ LAMBDA,
+
+ /**
+ * Bandwidth resource type.
+ */
+ BANDWIDTH,
+
+ /**
+ * MPLS label resource type.
+ */
+ MPLS_LABEL,
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceService.java
new file mode 100644
index 00000000..5468dfb7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceService.java
@@ -0,0 +1,85 @@
+/*
+ * 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.device;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Port;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentId;
+
+import java.util.Set;
+
+/**
+ * Service for providing device resources.
+ */
+@Beta
+public interface DeviceResourceService {
+ /**
+ * Request a set of ports needed to satisfy the intent.
+ *
+ * @param ports set of ports to allocate
+ * @param intent the intent
+ * @return true if ports were successfully allocated, false otherwise
+ */
+ boolean requestPorts(Set<Port> ports, Intent intent);
+
+ /**
+ * Returns the set of ports allocated for an intent.
+ *
+ * @param intentId the intent ID
+ * @return set of allocated ports
+ */
+ Set<Port> getAllocations(IntentId intentId);
+
+ /**
+ * Returns the intent allocated to a port.
+ *
+ * @param port the port
+ * @return intent ID allocated to the port
+ */
+ IntentId getAllocations(Port port);
+
+ /**
+ * Request a mapping between the given intents.
+ *
+ * @param keyIntentId the key intent ID
+ * @param valIntentId the value intent ID
+ * @return true if mapping was successful, false otherwise
+ */
+ boolean requestMapping(IntentId keyIntentId, IntentId valIntentId);
+
+ /**
+ * Returns the intents mapped to a lower intent.
+ *
+ * @param intentId the intent ID
+ * @return the set of intent IDs
+ */
+ Set<IntentId> getMapping(IntentId intentId);
+
+ /**
+ * Release mapping of given intent.
+ *
+ * @param intentId intent ID
+ */
+ void releaseMapping(IntentId intentId);
+
+ /**
+ * Release ports associated with given intent ID.
+ *
+ * @param intentId intent ID
+ */
+ void releasePorts(IntentId intentId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceStore.java
new file mode 100644
index 00000000..a52a843f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/DeviceResourceStore.java
@@ -0,0 +1,89 @@
+/*
+ * 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.device;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.intent.IntentId;
+
+import java.util.Set;
+
+public interface DeviceResourceStore {
+ /**
+ * Returns unallocated ports on the given device.
+ *
+ * @param deviceId device ID
+ * @return set of unallocated ports
+ */
+ Set<Port> getFreePorts(DeviceId deviceId);
+
+ /**
+ * Allocates the given ports to the given intent.
+ *
+ * @param ports set of ports to allocate
+ * @param intentId intent ID
+ * @return true if allocation was successful, false otherwise
+ */
+ boolean allocatePorts(Set<Port> ports, IntentId intentId);
+
+ /**
+ * Returns set of ports allocated for an intent.
+ *
+ * @param intentId the intent ID
+ * @return set of allocated ports
+ */
+ Set<Port> getAllocations(IntentId intentId);
+
+ /**
+ * Returns intent allocated to a port.
+ *
+ * @param port the port
+ * @return intent ID allocated to the port
+ */
+ IntentId getAllocations(Port port);
+
+ /**
+ * Allocates the mapping between the given intents.
+ *
+ * @param keyIntentId key intent ID
+ * @param valIntentId value intent ID
+ * @return true if mapping was successful, false otherwise
+ */
+ boolean allocateMapping(IntentId keyIntentId, IntentId valIntentId);
+
+ /**
+ * Returns the set of intents mapped to a lower intent.
+ *
+ * @param intentId intent ID
+ * @return set of intent IDs
+ */
+ Set<IntentId> getMapping(IntentId intentId);
+
+ /**
+ * Releases the mapping of the given intent.
+ *
+ * @param intentId intent ID
+ */
+ void releaseMapping(IntentId intentId);
+
+ /**
+ * Releases the ports allocated to the given intent.
+ *
+ * @param intentId intent ID
+ * @return true if release was successful, false otherwise
+ */
+ boolean releasePorts(IntentId intentId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/package-info.java
new file mode 100644
index 00000000..3ced37dc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/device/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.
+ */
+
+/**
+ * Services for reserving devices as network resources.
+ */
+package org.onosproject.net.resource.device;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResource.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResource.java
new file mode 100644
index 00000000..fe21e042
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResource.java
@@ -0,0 +1,72 @@
+/*
+ * 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.link;
+
+import org.onlab.util.Bandwidth;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of bandwidth resource in bps.
+ */
+public final class BandwidthResource implements LinkResource {
+
+ private final Bandwidth bandwidth;
+
+ /**
+ * Creates a new instance with given bandwidth.
+ *
+ * @param bandwidth bandwidth value to be assigned
+ */
+ public BandwidthResource(Bandwidth bandwidth) {
+ this.bandwidth = checkNotNull(bandwidth);
+ }
+
+ // Constructor for serialization
+ private BandwidthResource() {
+ this.bandwidth = null;
+ }
+
+ /**
+ * Returns bandwidth as a double value.
+ *
+ * @return bandwidth as a double value
+ */
+ public double toDouble() {
+ return bandwidth.bps();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof BandwidthResource) {
+ BandwidthResource that = (BandwidthResource) obj;
+ return Objects.equals(this.bandwidth, that.bandwidth);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.bandwidth);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(this.bandwidth);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceAllocation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceAllocation.java
new file mode 100644
index 00000000..74f6e102
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceAllocation.java
@@ -0,0 +1,77 @@
+/*
+ * 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.resource.link;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceType;
+
+import java.util.Objects;
+
+/**
+ * Representation of allocated bandwidth resource.
+ */
+public class BandwidthResourceAllocation implements ResourceAllocation {
+ private final BandwidthResource bandwidth;
+
+ /**
+ * Creates a new {@link BandwidthResourceRequest} with {@link BandwidthResource}
+ * object.
+ *
+ * @param bandwidth {@link BandwidthResource} object to be requested
+ */
+ public BandwidthResourceAllocation(BandwidthResource bandwidth) {
+ this.bandwidth = bandwidth;
+ }
+
+ /**
+ * Returns the bandwidth resource.
+ *
+ * @return the bandwidth resource
+ */
+ public BandwidthResource bandwidth() {
+ return bandwidth;
+ }
+
+ @Override
+ public ResourceType type() {
+ return ResourceType.BANDWIDTH;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(bandwidth);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final BandwidthResourceAllocation other = (BandwidthResourceAllocation) obj;
+ return Objects.equals(this.bandwidth, other.bandwidth());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("bandwidth", bandwidth)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceRequest.java
new file mode 100644
index 00000000..91cc3d19
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/BandwidthResourceRequest.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.resource.link;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+/**
+ * Representation of a request for bandwidth resource.
+ */
+public class BandwidthResourceRequest implements ResourceRequest {
+ private final BandwidthResource bandwidth;
+
+ /**
+ * Creates a new {@link BandwidthResourceRequest} with {@link BandwidthResource}
+ * object.
+ *
+ * @param bandwidth {@link BandwidthResource} object to be requested
+ */
+ public BandwidthResourceRequest(BandwidthResource bandwidth) {
+ this.bandwidth = bandwidth;
+ }
+
+ /**
+ * Returns the bandwidth resource.
+ *
+ * @return the bandwidth resource
+ */
+ public BandwidthResource bandwidth() {
+ return bandwidth;
+ }
+
+ @Override
+ public ResourceType type() {
+ return ResourceType.BANDWIDTH;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(bandwidth);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final BandwidthResourceAllocation other = (BandwidthResourceAllocation) obj;
+ return Objects.equals(this.bandwidth, other.bandwidth());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("bandwidth", bandwidth)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceAllocations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceAllocations.java
new file mode 100644
index 00000000..379bf71e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceAllocations.java
@@ -0,0 +1,112 @@
+/*
+ * 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.resource.link;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+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 java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Implementation of {@link LinkResourceAllocations}.
+ */
+public class DefaultLinkResourceAllocations implements LinkResourceAllocations {
+ private final LinkResourceRequest request;
+ // TODO: probably should be using LinkKey instead
+ private final Map<Link, Set<ResourceAllocation>> allocations;
+
+ /**
+ * Creates a new link resource allocations.
+ *
+ * @param request requested resources
+ * @param allocations allocated resources
+ */
+ public DefaultLinkResourceAllocations(LinkResourceRequest request,
+ Map<Link, Set<ResourceAllocation>> allocations) {
+ this.request = checkNotNull(request);
+ ImmutableMap.Builder<Link, Set<ResourceAllocation>> builder
+ = ImmutableMap.builder();
+ for (Entry<Link, Set<ResourceAllocation>> e : allocations.entrySet()) {
+ builder.put(e.getKey(), ImmutableSet.copyOf(e.getValue()));
+ }
+ this.allocations = builder.build();
+ }
+
+ public IntentId intentId() {
+ return request.intentId();
+ }
+
+ public Collection<Link> links() {
+ return request.links();
+ }
+
+ public Set<ResourceRequest> resources() {
+ return request.resources();
+ }
+
+ @Override
+ public ResourceType type() {
+ return null;
+ }
+
+ @Override
+ public Set<ResourceAllocation> getResourceAllocation(Link link) {
+ Set<ResourceAllocation> result = allocations.get(link);
+ if (result == null) {
+ result = Collections.emptySet();
+ }
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(request, allocations);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final DefaultLinkResourceAllocations other = (DefaultLinkResourceAllocations) obj;
+ return Objects.equals(this.request, other.request)
+ && Objects.equals(this.allocations, other.allocations);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("allocations", allocations)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceRequest.java
new file mode 100644
index 00000000..5153aebf
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/DefaultLinkResourceRequest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.link;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.IntentId;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+/**
+ * Implementation of {@link LinkResourceRequest}.
+ */
+public final class DefaultLinkResourceRequest implements LinkResourceRequest {
+
+ private final IntentId intentId;
+ private final Collection<Link> links;
+ private final Set<ResourceRequest> resources;
+
+ /**
+ * Creates a new link resource request with the given ID, links, and
+ * resource requests.
+ *
+ * @param intentId intent ID related to this request
+ * @param links a set of links for the request
+ * @param resources a set of resources to be requested
+ */
+ private DefaultLinkResourceRequest(IntentId intentId,
+ Collection<Link> links,
+ Set<ResourceRequest> resources) {
+ this.intentId = intentId;
+ this.links = ImmutableSet.copyOf(links);
+ this.resources = ImmutableSet.copyOf(resources);
+ }
+
+
+ @Override
+ public ResourceType type() {
+ return null;
+ }
+
+ @Override
+ public IntentId intentId() {
+ return intentId;
+ }
+
+ @Override
+ public Collection<Link> links() {
+ return links;
+ }
+
+ @Override
+ public Set<ResourceRequest> resources() {
+ return resources;
+ }
+
+ /**
+ * Returns builder of link resource request.
+ *
+ * @param intentId intent ID related to this request
+ * @param links a set of links for the request
+ * @return builder of link resource request
+ */
+ public static LinkResourceRequest.Builder builder(
+ IntentId intentId, Collection<Link> links) {
+ return new Builder(intentId, links);
+ }
+
+ /**
+ * Builder of link resource request.
+ */
+ public static final class Builder implements LinkResourceRequest.Builder {
+ private IntentId intentId;
+ private Collection<Link> links;
+ private Set<ResourceRequest> resources;
+
+ /**
+ * Creates a new link resource request.
+ *
+ * @param intentId intent ID related to this request
+ * @param links a set of links for the request
+ */
+ private Builder(IntentId intentId, Collection<Link> links) {
+ this.intentId = intentId;
+ this.links = links;
+ this.resources = new HashSet<>();
+ }
+
+ /**
+ * Adds lambda request.
+ *
+ * @return self
+ */
+ @Override
+ public Builder addLambdaRequest() {
+ resources.add(new LambdaResourceRequest());
+ return this;
+ }
+
+ /**
+ * Adds Mpls request.
+ *
+ * @return self
+ */
+ @Override
+ public Builder addMplsRequest() {
+ resources.add(new MplsLabelResourceRequest());
+ return this;
+ }
+
+ /**
+ * Adds bandwidth request with bandwidth value.
+ *
+ * @param bandwidth bandwidth value in bits per second to be requested
+ * @return self
+ */
+ @Override
+ public Builder addBandwidthRequest(double bandwidth) {
+ resources.add(new BandwidthResourceRequest(new BandwidthResource(Bandwidth.bps(bandwidth))));
+ return this;
+ }
+
+ @Override
+ public LinkResourceRequest.Builder addConstraint(Constraint constraint) {
+ if (constraint instanceof LambdaConstraint) {
+ return addLambdaRequest();
+ } else if (constraint instanceof BandwidthConstraint) {
+ BandwidthConstraint bw = (BandwidthConstraint) constraint;
+ return addBandwidthRequest(bw.bandwidth().toDouble());
+ }
+ return this;
+ }
+
+ /**
+ * Returns link resource request.
+ *
+ * @return link resource request
+ */
+ @Override
+ public LinkResourceRequest build() {
+ return new DefaultLinkResourceRequest(intentId, links, resources);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(intentId, links);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final DefaultLinkResourceRequest other = (DefaultLinkResourceRequest) obj;
+ return Objects.equals(this.intentId, other.intentId)
+ && Objects.equals(this.links, other.links);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResource.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResource.java
new file mode 100644
index 00000000..3733e467
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResource.java
@@ -0,0 +1,93 @@
+/*
+ * 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.resource.link;
+
+import org.onosproject.net.IndexedLambda;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of lambda resource.
+ */
+public final class LambdaResource implements LinkResource {
+
+ private final IndexedLambda lambda;
+
+ /**
+ * Creates a new instance with given lambda.
+ *
+ * @param lambda lambda to be assigned
+ */
+ private LambdaResource(IndexedLambda lambda) {
+ this.lambda = checkNotNull(lambda);
+ }
+
+ // Constructor for serialization
+ private LambdaResource() {
+ this.lambda = null;
+ }
+
+ /**
+ * Creates a new instance with the given index of lambda.
+ *
+ * @param lambda index value of lambda to be assigned
+ * @return {@link LambdaResource} instance with the given lambda
+ */
+ public static LambdaResource valueOf(int lambda) {
+ return valueOf(new IndexedLambda(lambda));
+ }
+
+ /**
+ * Creates a new instance with the given lambda.
+ *
+ * @param lambda lambda to be assigned
+ * @return {@link LambdaResource} instance with the given lambda
+ */
+ public static LambdaResource valueOf(IndexedLambda lambda) {
+ return new LambdaResource(lambda);
+ }
+
+ /**
+ * Returns lambda as an int value.
+ *
+ * @return lambda as an int value
+ */
+ public int toInt() {
+ return (int) lambda.index();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof LambdaResource) {
+ LambdaResource that = (LambdaResource) obj;
+ return Objects.equals(this.lambda, that.lambda);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return lambda.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(this.lambda);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceAllocation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceAllocation.java
new file mode 100644
index 00000000..545f025f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceAllocation.java
@@ -0,0 +1,77 @@
+/*
+ * 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.resource.link;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceType;
+
+import java.util.Objects;
+
+/**
+ * Representation of allocated lambda resource.
+ */
+public class LambdaResourceAllocation implements ResourceAllocation {
+ private final LambdaResource lambda;
+
+ @Override
+ public ResourceType type() {
+ return ResourceType.LAMBDA;
+ }
+
+ /**
+ * Creates a new {@link LambdaResourceAllocation} with {@link LambdaResource}
+ * object.
+ *
+ * @param lambda allocated lambda
+ */
+ public LambdaResourceAllocation(LambdaResource lambda) {
+ this.lambda = lambda;
+ }
+
+ /**
+ * Returns the lambda resource.
+ *
+ * @return the lambda resource
+ */
+ public LambdaResource lambda() {
+ return lambda;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(lambda);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final LambdaResourceAllocation other = (LambdaResourceAllocation) obj;
+ return Objects.equals(this.lambda, other.lambda);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lambda", lambda)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceRequest.java
new file mode 100644
index 00000000..b0391f5a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LambdaResourceRequest.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.resource.link;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+/**
+ * Representation of a request for lambda resource.
+ */
+public class LambdaResourceRequest implements ResourceRequest {
+
+ @Override
+ public ResourceType type() {
+ return ResourceType.LAMBDA;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResource.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResource.java
new file mode 100644
index 00000000..ec06611e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResource.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.
+ */
+package org.onosproject.net.resource.link;
+
+/**
+ * Abstraction of link resource.
+ */
+public interface LinkResource {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceAllocations.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceAllocations.java
new file mode 100644
index 00000000..7828867c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceAllocations.java
@@ -0,0 +1,59 @@
+/*
+ * 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.resource.link;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceRequest;
+
+/**
+ * Representation of allocated link resources.
+ */
+public interface LinkResourceAllocations extends ResourceAllocation {
+
+ /**
+ * Returns the {@link IntentId} associated with the request.
+ *
+ * @return the {@link IntentId} associated with the request
+ */
+ IntentId intentId();
+
+ /**
+ * Returns the set of target links.
+ *
+ * @return the set of target links
+ */
+ Collection<Link> links();
+
+ /**
+ * Returns the set of resource requests.
+ *
+ * @return the set of resource requests
+ */
+ Set<ResourceRequest> resources();
+
+ /**
+ * Returns allocated resource for the given link.
+ *
+ * @param link the target link
+ * @return allocated resource for the link
+ */
+ Set<ResourceAllocation> getResourceAllocation(Link link);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceEvent.java
new file mode 100644
index 00000000..3edb386a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceEvent.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.net.resource.link;
+
+import java.util.Collection;
+
+import org.onosproject.event.AbstractEvent;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Describes an event related to a Link Resource.
+ */
+public final class LinkResourceEvent
+ extends AbstractEvent<LinkResourceEvent.Type, Collection<LinkResourceAllocations>> {
+
+ /**
+ * Type of resource this event is for.
+ */
+ public enum Type {
+ /** Additional resources are now available. */
+ ADDITIONAL_RESOURCES_AVAILABLE
+ }
+
+ /**
+ * Constructs a link resource event.
+ *
+ * @param type type of resource event to create
+ * @param linkResourceAllocations allocations that are now available
+ */
+ public LinkResourceEvent(Type type,
+ Collection<LinkResourceAllocations> linkResourceAllocations) {
+ super(type, ImmutableList.copyOf(linkResourceAllocations));
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceListener.java
new file mode 100644
index 00000000..599dd4fb
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.resource.link;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity for receiving link resource events.
+ */
+public interface LinkResourceListener extends EventListener<LinkResourceEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceRequest.java
new file mode 100644
index 00000000..8023a92e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceRequest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.link;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.resource.ResourceRequest;
+
+/**
+ * Representation of a request for link resource.
+ */
+public interface LinkResourceRequest extends ResourceRequest {
+
+ /**
+ * Returns the {@link IntentId} associated with the request.
+ *
+ * @return the {@link IntentId} associated with the request
+ */
+ IntentId intentId();
+
+ /**
+ * Returns the set of target links.
+ *
+ * @return the set of target links
+ */
+ Collection<Link> links();
+
+ /**
+ * Returns the set of resource requests.
+ *
+ * @return the set of resource requests
+ */
+ Set<ResourceRequest> resources();
+
+ /**
+ * Builder of link resource request.
+ */
+ interface Builder {
+ /**
+ * Adds lambda request.
+ *
+ * @return self
+ */
+ Builder addLambdaRequest();
+
+ /**
+ * Adds MPLS request.
+ *
+ * @return self
+ */
+ Builder addMplsRequest();
+
+ /**
+ * Adds bandwidth request with bandwidth value.
+ *
+ * @param bandwidth bandwidth value to be requested
+ * @return self
+ */
+ Builder addBandwidthRequest(double bandwidth);
+
+ /**
+ * Adds the resources required for a constraint.
+ *
+ * @param constraint the constraint
+ * @return self
+ */
+ Builder addConstraint(Constraint constraint);
+
+ /**
+ * Returns link resource request.
+ *
+ * @return link resource request
+ */
+ LinkResourceRequest build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceService.java
new file mode 100644
index 00000000..6dc04dfc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceService.java
@@ -0,0 +1,95 @@
+/*
+ * 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.resource.link;
+
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.resource.ResourceRequest;
+
+/**
+ * Service for providing link resource allocation.
+ */
+public interface LinkResourceService
+ extends ListenerService<LinkResourceEvent, LinkResourceListener> {
+
+ /**
+ * Requests resources.
+ *
+ * @param req resources to be allocated
+ * @return allocated resources
+ */
+ LinkResourceAllocations requestResources(LinkResourceRequest req);
+
+ /**
+ * Releases resources.
+ *
+ * @param allocations resources to be released
+ */
+ void releaseResources(LinkResourceAllocations allocations);
+
+ /**
+ * Updates previously made allocations with a new resource request.
+ *
+ * @param req updated resource request
+ * @param oldAllocations old resource allocations
+ * @return new resource allocations
+ */
+ LinkResourceAllocations updateResources(LinkResourceRequest req,
+ LinkResourceAllocations oldAllocations);
+
+ /**
+ * Returns all allocated resources.
+ *
+ * @return allocated resources
+ */
+ Iterable<LinkResourceAllocations> getAllocations();
+
+ /**
+ * Returns all allocated resources to given link.
+ *
+ * @param link a target link
+ * @return allocated resources
+ */
+ Iterable<LinkResourceAllocations> getAllocations(Link link);
+
+ /**
+ * Returns the resources allocated for an Intent.
+ *
+ * @param intentId the target Intent's id
+ * @return allocated resources for Intent
+ */
+ LinkResourceAllocations getAllocations(IntentId intentId);
+
+ /**
+ * Returns available resources for given link.
+ *
+ * @param link a target link
+ * @return available resources for the target link
+ */
+ Iterable<ResourceRequest> getAvailableResources(Link link);
+
+ /**
+ * Returns available resources for given link.
+ *
+ * @param link a target link
+ * @param allocations allocations to be included as available
+ * @return available resources for the target link
+ */
+ Iterable<ResourceRequest> getAvailableResources(Link link,
+ LinkResourceAllocations allocations);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStore.java
new file mode 100644
index 00000000..e6674dbd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStore.java
@@ -0,0 +1,73 @@
+/*
+ * 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.resource.link;
+
+import java.util.Set;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.resource.ResourceAllocation;
+
+/**
+ * Manages link resources.
+ */
+public interface LinkResourceStore {
+ /**
+ * Returns free resources for given link.
+ *
+ * @param link a target link
+ * @return free resources for given link
+ */
+ Set<ResourceAllocation> getFreeResources(Link link);
+
+ /**
+ * Allocates resources.
+ *
+ * @param allocations resources to be allocated
+ */
+ void allocateResources(LinkResourceAllocations allocations);
+
+ /**
+ * Releases resources.
+ *
+ * @param allocations resources to be released
+ * @return the link resource event
+ */
+ LinkResourceEvent releaseResources(LinkResourceAllocations allocations);
+
+ /**
+ * Returns resources allocated for an Intent.
+ *
+ * @param intentId the target Intent's ID
+ * @return allocated resources or null if no resource is allocated
+ */
+ LinkResourceAllocations getAllocations(IntentId intentId);
+
+ /**
+ * Returns resources allocated for a link.
+ *
+ * @param link the target link
+ * @return allocated resources
+ */
+ Iterable<LinkResourceAllocations> getAllocations(Link link);
+
+ /**
+ * Returns all allocated resources.
+ *
+ * @return allocated resources
+ */
+ Iterable<LinkResourceAllocations> getAllocations();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStoreDelegate.java
new file mode 100644
index 00000000..6c051d6a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResourceStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.resource.link;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Link resource store delegate abstraction.
+ */
+public interface LinkResourceStoreDelegate extends StoreDelegate<LinkResourceEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResources.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResources.java
new file mode 100644
index 00000000..dc005227
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/LinkResources.java
@@ -0,0 +1,63 @@
+/*
+ * 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.resource.link;
+
+import java.util.Set;
+
+/**
+ * Abstraction of a resources of a link.
+ */
+public interface LinkResources {
+
+ /**
+ * Returns resources as a set of {@link LinkResource}s.
+ *
+ * @return a set of {@link LinkResource}s
+ */
+ Set<LinkResource> resources();
+
+ /**
+ * Builder of {@link LinkResources}.
+ */
+ interface Builder {
+
+ /**
+ * Adds bandwidth resource.
+ * <p>
+ * This operation adds given bandwidth to previous bandwidth and
+ * generates single bandwidth resource.
+ *
+ * @param bandwidth bandwidth value to be added
+ * @return self
+ */
+ Builder addBandwidth(double bandwidth);
+
+ /**
+ * Adds lambda resource.
+ *
+ * @param lambda lambda value to be added
+ * @return self
+ */
+ Builder addLambda(int lambda);
+
+ /**
+ * Builds an immutable link resources.
+ *
+ * @return link resources
+ */
+ LinkResources build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabel.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabel.java
new file mode 100644
index 00000000..89c87760
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabel.java
@@ -0,0 +1,75 @@
+/*
+ * 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.link;
+
+import java.util.Objects;
+
+/**
+ * Representation of MPLS label resource.
+ */
+public final class MplsLabel implements LinkResource {
+
+ private final org.onlab.packet.MplsLabel mplsLabel;
+
+
+ /**
+ * Creates a new instance with given MPLS label.
+ *
+ * @param mplsLabel MPLS Label value to be assigned
+ */
+ public MplsLabel(int mplsLabel) {
+ this.mplsLabel = org.onlab.packet.MplsLabel.mplsLabel(mplsLabel);
+ }
+
+ /**
+ * Creates a new instance with given MPLS label.
+ *
+ * @param mplsLabel mplsLabel value to be assigned
+ * @return {@link MplsLabel} instance with given bandwidth
+ */
+ public static MplsLabel valueOf(int mplsLabel) {
+ return new MplsLabel(mplsLabel);
+ }
+
+ /**
+ * Returns MPLS Label as an MPLS Label Object.
+ *
+ * @return MPLS label as an MPLS Label Object.
+ */
+ public org.onlab.packet.MplsLabel label() {
+ return mplsLabel;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MplsLabel) {
+ MplsLabel that = (MplsLabel) obj;
+ return Objects.equals(this.mplsLabel, that.mplsLabel);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.mplsLabel);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(this.mplsLabel);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceAllocation.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceAllocation.java
new file mode 100644
index 00000000..10911539
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceAllocation.java
@@ -0,0 +1,78 @@
+ /*
+ * 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.link;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceType;
+
+import java.util.Objects;
+
+/**
+ * Representation of allocated MPLS label resource.
+ */
+public class MplsLabelResourceAllocation implements ResourceAllocation {
+ private final MplsLabel mplsLabel;
+
+ @Override
+ public ResourceType type() {
+ return ResourceType.MPLS_LABEL;
+ }
+
+ /**
+ * Creates a new {@link MplsLabelResourceAllocation} with {@link MplsLabel}
+ * object.
+ *
+ * @param mplsLabel allocated MPLS Label
+ */
+ public MplsLabelResourceAllocation(MplsLabel mplsLabel) {
+ this.mplsLabel = mplsLabel;
+ }
+
+ /**
+ * Returns the MPLS label resource.
+ *
+ * @return the MPLS label resource
+ */
+ public MplsLabel mplsLabel() {
+ return mplsLabel;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mplsLabel);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final MplsLabelResourceAllocation other = (MplsLabelResourceAllocation) obj;
+ return Objects.equals(this.mplsLabel, other.mplsLabel);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("mplsLabel", mplsLabel)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceRequest.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceRequest.java
new file mode 100644
index 00000000..0a03f450
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/MplsLabelResourceRequest.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.resource.link;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+/**
+ * Representation of a request for lambda resource.
+ */
+public class MplsLabelResourceRequest implements ResourceRequest {
+
+ @Override
+ public ResourceType type() {
+ return ResourceType.MPLS_LABEL;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/package-info.java
new file mode 100644
index 00000000..b10e4ba4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/link/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Services for reserving links and their capacity as network resources,
+ * e.g.&nbsp;bandwidth, lambdas.
+ */
+package org.onosproject.net.resource.link;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/package-info.java
new file mode 100644
index 00000000..e676fc87
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/resource/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.
+ */
+
+/**
+ * Abstractions for reserving network resources.
+ */
+package org.onosproject.net.resource;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/DefaultLoad.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/DefaultLoad.java
new file mode 100644
index 00000000..45e85372
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/DefaultLoad.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Implementation of a load.
+ */
+public class DefaultLoad implements Load {
+
+ private final boolean isValid;
+ private final long current;
+ private final long previous;
+ private final long time;
+ private final long interval;
+
+ /**
+ * Indicates the flow statistics poll interval in seconds.
+ */
+ private static long pollInterval = 10;
+
+ /**
+ * Creates an invalid load.
+ */
+ public DefaultLoad() {
+ this.isValid = false;
+ this.time = System.currentTimeMillis();
+ this.current = -1;
+ this.previous = -1;
+ this.interval = pollInterval;
+ }
+
+ /**
+ * Creates a load value from the parameters.
+ *
+ * @param current the current value
+ * @param previous the previous value
+ */
+ public DefaultLoad(long current, long previous) {
+ this(current, previous, pollInterval);
+ }
+
+ /**
+ * Creates a load value from the parameters.
+ *
+ * @param current the current value
+ * @param previous the previous value
+ * @param interval poll interval for this load
+ */
+ public DefaultLoad(long current, long previous, long interval) {
+ checkArgument(interval > 0, "Interval must be greater than 0");
+ this.current = current;
+ this.previous = previous;
+ this.time = System.currentTimeMillis();
+ this.isValid = true;
+ this.interval = interval;
+ }
+
+ /**
+ * Sets the poll interval in seconds. Used solely for the purpose of
+ * computing the load.
+ *
+ * @param newPollInterval poll interval duration in seconds
+ */
+ public static void setPollInterval(long newPollInterval) {
+ pollInterval = newPollInterval;
+ }
+
+ @Override
+ public long rate() {
+ return (current - previous) / interval;
+ }
+
+ @Override
+ public long latest() {
+ return current;
+ }
+
+ @Override
+ public boolean isValid() {
+ return isValid;
+ }
+
+ @Override
+ public long time() {
+ return time;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper("Load").add("rate", rate())
+ .add("latest", latest()).toString();
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/Load.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/Load.java
new file mode 100644
index 00000000..38fed04e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/Load.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Simple data repository for link load information.
+ */
+public interface Load {
+
+ /**
+ * Obtain the current observed rate (in bytes/s) on a link.
+ *
+ * @return long value
+ */
+ long rate();
+
+ /**
+ * Obtain the latest bytes counter viewed on that link.
+ *
+ * @return long value
+ */
+ long latest();
+
+ /**
+ * Indicates whether this load was built on valid values.
+ *
+ * @return boolean
+ */
+ boolean isValid();
+
+ /**
+ * Returns when this value was seen.
+ *
+ * @return epoch time
+ */
+ long time();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticService.java
new file mode 100644
index 00000000..b9f7cc91
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticService.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+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.FlowRule;
+
+import java.util.Optional;
+
+/**
+ * Service for obtaining statistic information about link in the system.
+ * Statistics are obtained from the FlowRuleService in order to minimize the
+ * amount of hammering occuring at the dataplane.
+ */
+public interface StatisticService {
+
+ /**
+ * Obtain the load for a the ingress to the given link.
+ *
+ * @param link the link to query.
+ * @return a {@link org.onosproject.net.statistic.Load Load}
+ */
+ Load load(Link link);
+
+ /**
+ * Obtain the load for the given port.
+ *
+ * @param connectPoint the port to query
+ * @return a {@link org.onosproject.net.statistic.Load}
+ */
+ Load load(ConnectPoint connectPoint);
+
+ /**
+ * Find the most loaded link along a path.
+ *
+ * @param path the path to search in
+ * @return the most loaded {@link org.onosproject.net.Link}.
+ */
+ Link max(Path path);
+
+ /**
+ * Find the least loaded link along a path.
+ *
+ * @param path the path to search in
+ * @return the least loaded {@link org.onosproject.net.Link}.
+ */
+ Link min(Path path);
+
+ /**
+ * Returns the highest hitter (a flow rule) for a given port, ie. the
+ * flow rule which is generating the most load.
+ *
+ * @param connectPoint the port
+ * @return the flow rule
+ */
+ FlowRule highestHitter(ConnectPoint connectPoint);
+
+ /**
+ * Obtain the load for a the ingress to the given link used by
+ * the specified application ID and group ID.
+ *
+ * @param link link to query
+ * @param appId application ID to filter with
+ * @param groupId group ID to filter with
+ * @return {@link Load Load}
+ */
+ Load load(Link link, ApplicationId appId, Optional<GroupId> groupId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticStore.java
new file mode 100644
index 00000000..8566ef5b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/StatisticStore.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.net.statistic;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+
+import java.util.Set;
+
+/**
+ * Store to house the computed statistics.
+ */
+public interface StatisticStore {
+
+ /**
+ * Lay the foundation for receiving flow stats for this rule.
+ *
+ * @param rule a {@link org.onosproject.net.flow.FlowRule}
+ */
+ void prepareForStatistics(FlowRule rule);
+
+ /**
+ * Remove entries associated with this rule.
+ *
+ * @param rule {@link org.onosproject.net.flow.FlowRule}
+ */
+ void removeFromStatistics(FlowRule rule);
+
+ /**
+ * Adds a stats observation for a flow rule.
+ *
+ * @param rule a {@link org.onosproject.net.flow.FlowEntry}
+ */
+ void addOrUpdateStatistic(FlowEntry rule);
+
+ /**
+ * Fetches the current observed stats values.
+ *
+ * @param connectPoint the port to fetch information for
+ * @return set of current flow rules
+ */
+ Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint);
+
+ /**
+ * Fetches the current observed stats values.
+ *
+ * @param connectPoint the port to fetch information for
+ * @return set of current values
+ */
+ Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/package-info.java
new file mode 100644
index 00000000..37889f3d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/statistic/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.
+ */
+
+/**
+ * Service for looking up statistics on links.
+ */
+package org.onosproject.net.statistic;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/ClusterId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/ClusterId.java
new file mode 100644
index 00000000..676f0068
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/ClusterId.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Representation of the topology cluster identity.
+ */
+public final class ClusterId {
+
+ private final int id;
+
+ // Public construction is prohibit
+ private ClusterId(int id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the cluster identifier, represented by the specified integer
+ * serial number.
+ *
+ * @param id integer serial number
+ * @return cluster identifier
+ */
+ public static ClusterId clusterId(int id) {
+ return new ClusterId(id);
+ }
+
+ /**
+ * Returns the backing integer index.
+ *
+ * @return backing integer index
+ */
+ public int index() {
+ return id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ClusterId) {
+ final ClusterId other = (ClusterId) obj;
+ return Objects.equals(this.id, other.id);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("id", id).toString();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java
new file mode 100644
index 00000000..f1e20dac
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.SparseAnnotations;
+import org.slf4j.Logger;
+
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Default implementation of an immutable topology graph data carrier.
+ */
+public class DefaultGraphDescription extends AbstractDescription
+ implements GraphDescription {
+
+ private static final Logger log = getLogger(DefaultGraphDescription.class);
+
+ private final long nanos;
+ private final long creationTime;
+ private final ImmutableSet<TopologyVertex> vertexes;
+ private final ImmutableSet<TopologyEdge> edges;
+
+ private final Map<DeviceId, TopologyVertex> vertexesById = Maps.newHashMap();
+
+ /**
+ * Creates a minimal topology graph description to allow core to construct
+ * and process the topology graph.
+ *
+ * @param nanos time in nanos of when the topology description was created
+ * @param devices collection of infrastructure devices
+ * @param links collection of infrastructure links
+ * @param annotations optional key/value annotations map
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ public DefaultGraphDescription(long nanos, Iterable<Device> devices,
+ Iterable<Link> links,
+ SparseAnnotations... annotations) {
+ this(nanos, System.currentTimeMillis(), devices, links, annotations);
+ }
+
+ /**
+ * Creates a minimal topology graph description to allow core to construct
+ * and process the topology graph.
+ *
+ * @param nanos time in nanos of when the topology description was created
+ * @param millis time in millis of when the topology description was created
+ * @param devices collection of infrastructure devices
+ * @param links collection of infrastructure links
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultGraphDescription(long nanos, long millis,
+ Iterable<Device> devices,
+ Iterable<Link> links,
+ SparseAnnotations... annotations) {
+ super(annotations);
+ this.nanos = nanos;
+ this.creationTime = millis;
+ this.vertexes = buildVertexes(devices);
+ this.edges = buildEdges(links);
+ vertexesById.clear();
+ }
+
+ @Override
+ public long timestamp() {
+ return nanos;
+ }
+
+ @Override
+ public long creationTime() {
+ return creationTime;
+ }
+
+ @Override
+ public ImmutableSet<TopologyVertex> vertexes() {
+ return vertexes;
+ }
+
+ @Override
+ public ImmutableSet<TopologyEdge> edges() {
+ return edges;
+ }
+
+ // Builds a set of topology vertexes from the specified list of devices
+ private ImmutableSet<TopologyVertex> buildVertexes(Iterable<Device> devices) {
+ ImmutableSet.Builder<TopologyVertex> vertexes = ImmutableSet.builder();
+ for (Device device : devices) {
+ TopologyVertex vertex = new DefaultTopologyVertex(device.id());
+ vertexes.add(vertex);
+ vertexesById.put(vertex.deviceId(), vertex);
+ }
+ return vertexes.build();
+ }
+
+ // Builds a set of topology vertexes from the specified list of links
+ private ImmutableSet<TopologyEdge> buildEdges(Iterable<Link> links) {
+ ImmutableSet.Builder<TopologyEdge> edges = ImmutableSet.builder();
+ for (Link link : links) {
+ try {
+ edges.add(new DefaultTopologyEdge(vertexOf(link.src()),
+ vertexOf(link.dst()),
+ link));
+ } catch (IllegalArgumentException e) {
+ log.debug("Ignoring {}, missing vertex", link);
+ }
+ }
+ return edges.build();
+ }
+
+ // Fetches a vertex corresponding to the given connection point device.
+ private TopologyVertex vertexOf(ConnectPoint connectPoint) {
+ DeviceId id = connectPoint.deviceId();
+ TopologyVertex vertex = vertexesById.get(id);
+ checkArgument(vertex != null, "Vertex missing for %s", id);
+ return vertex;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyCluster.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyCluster.java
new file mode 100644
index 00000000..ac32316c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyCluster.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of a network topology cluster.
+ */
+public class DefaultTopologyCluster implements TopologyCluster {
+
+ private final ClusterId id;
+ private final int deviceCount;
+ private final int linkCount;
+ private final TopologyVertex root;
+
+ /**
+ * Creates a new topology cluster descriptor with the specified attributes.
+ *
+ * @param id cluster id
+ * @param deviceCount number of devices in the cluster
+ * @param linkCount number of links in the cluster
+ * @param root cluster root node
+ */
+ public DefaultTopologyCluster(ClusterId id, int deviceCount, int linkCount,
+ TopologyVertex root) {
+ this.id = id;
+ this.deviceCount = deviceCount;
+ this.linkCount = linkCount;
+ this.root = root;
+ }
+
+ @Override
+ public ClusterId id() {
+ return id;
+ }
+
+ @Override
+ public int deviceCount() {
+ return deviceCount;
+ }
+
+ @Override
+ public int linkCount() {
+ return linkCount;
+ }
+
+ @Override
+ public TopologyVertex root() {
+ return root;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, deviceCount, linkCount, root);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultTopologyCluster) {
+ final DefaultTopologyCluster other = (DefaultTopologyCluster) obj;
+ return Objects.equals(this.id, other.id) &&
+ Objects.equals(this.deviceCount, other.deviceCount) &&
+ Objects.equals(this.linkCount, other.linkCount) &&
+ Objects.equals(this.root, other.root);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("id", id)
+ .add("deviceCount", deviceCount)
+ .add("linkCount", linkCount)
+ .add("root", root)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyEdge.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyEdge.java
new file mode 100644
index 00000000..dacb5fd8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyEdge.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import org.onosproject.net.Link;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implementation of the topology edge backed by a link.
+ */
+public class DefaultTopologyEdge implements TopologyEdge {
+
+ private final Link link;
+ private final TopologyVertex src;
+ private final TopologyVertex dst;
+
+ /**
+ * Creates a new topology edge.
+ *
+ * @param src source vertex
+ * @param dst destination vertex
+ * @param link infrastructure link
+ */
+ public DefaultTopologyEdge(TopologyVertex src, TopologyVertex dst, Link link) {
+ this.src = src;
+ this.dst = dst;
+ this.link = link;
+ }
+
+ @Override
+ public Link link() {
+ return link;
+ }
+
+ @Override
+ public TopologyVertex src() {
+ return src;
+ }
+
+ @Override
+ public TopologyVertex dst() {
+ return dst;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(link);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultTopologyEdge) {
+ final DefaultTopologyEdge other = (DefaultTopologyEdge) obj;
+ return Objects.equals(this.link, other.link);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this).add("src", src).add("dst", dst).toString();
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyVertex.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyVertex.java
new file mode 100644
index 00000000..07a09cbd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/DefaultTopologyVertex.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * Implementation of the topology vertex backed by a device id.
+ */
+public class DefaultTopologyVertex implements TopologyVertex {
+
+ private final DeviceId deviceId;
+
+ /**
+ * Creates a new topology vertex.
+ *
+ * @param deviceId backing infrastructure device identifier
+ */
+ public DefaultTopologyVertex(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(deviceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultTopologyVertex) {
+ final DefaultTopologyVertex other = (DefaultTopologyVertex) obj;
+ return Objects.equals(this.deviceId, other.deviceId);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return deviceId.toString();
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java
new file mode 100644
index 00000000..806a5a45
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import org.onosproject.net.Description;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Describes attribute(s) of a network graph.
+ */
+public interface GraphDescription extends Description {
+
+ /**
+ * Returns the creation timestamp of the graph description. This is
+ * expressed in system nanos to allow proper sequencing.
+ *
+ * @return graph description creation timestamp
+ */
+ long timestamp();
+
+ /**
+ * Returns the creation timestamp of the graph description. This is
+ * expressed in system millis to allow proper date and time formatting.
+ *
+ * @return graph description creation timestamp in millis
+ */
+ long creationTime();
+
+ /**
+ * Returns the set of topology graph vertexes.
+ *
+ * @return set of graph vertexes
+ */
+ ImmutableSet<TopologyVertex> vertexes();
+
+ /**
+ * Returns the set of topology graph edges.
+ *
+ * @return set of graph edges
+ */
+ ImmutableSet<TopologyEdge> edges();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/LinkWeight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/LinkWeight.java
new file mode 100644
index 00000000..a19abd40
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/LinkWeight.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onlab.graph.EdgeWeight;
+
+/**
+ * Entity capable of determining cost or weight of a specified topology
+ * graph edge.
+ */
+public interface LinkWeight extends EdgeWeight<TopologyVertex, TopologyEdge> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/PathService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/PathService.java
new file mode 100644
index 00000000..be8c7cfc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/PathService.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Path;
+
+import java.util.Set;
+
+/**
+ * Service for obtaining pre-computed paths or for requesting computation of
+ * paths using the current topology snapshot.
+ */
+public interface PathService {
+
+ /**
+ * Returns the set of all shortest paths, precomputed in terms of hop-count,
+ * between the specified source and destination elements.
+ *
+ * @param src source element
+ * @param dst destination element
+ * @return set of all shortest paths between the two elements
+ */
+ Set<Path> getPaths(ElementId src, ElementId dst);
+
+ /**
+ * Returns the set of all shortest paths, computed using the supplied
+ * edge-weight entity, between the specified source and destination
+ * network elements.
+ *
+ * @param src source element
+ * @param dst destination element
+ * @param weight edge-weight entity
+ * @return set of all shortest paths between the two element
+ */
+ Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/Topology.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/Topology.java
new file mode 100644
index 00000000..6337807c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/Topology.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import org.onosproject.net.Provided;
+
+/**
+ * Represents a network topology computation snapshot.
+ */
+public interface Topology extends Provided {
+
+ /**
+ * Returns the time, specified in system nanos of when the topology became
+ * available.
+ *
+ * @return time in system nanos
+ */
+ long time();
+
+ /**
+ * Returns the time, specified in system millis of when the topology became
+ * available.
+ *
+ * @return time in system nanos
+ */
+ long creationTime();
+
+ /**
+ * Returns the time, specified in system nanos of how long the topology took
+ * to compute.
+ *
+ * @return elapsed time in system nanos
+ */
+ long computeCost();
+
+ /**
+ * Returns the number of SCCs (strongly connected components) in the
+ * topology.
+ *
+ * @return number of clusters
+ */
+ int clusterCount();
+
+ /**
+ * Returns the number of infrastructure devices in the topology.
+ *
+ * @return number of devices
+ */
+ int deviceCount();
+
+ /**
+ * Returns the number of infrastructure links in the topology.
+ *
+ * @return number of links
+ */
+ int linkCount();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyCluster.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyCluster.java
new file mode 100644
index 00000000..8e685534
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyCluster.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Representation of an SCC (strongly-connected component) in a network topology.
+ */
+public interface TopologyCluster {
+
+ /**
+ * Returns the cluster id.
+ *
+ * @return cluster identifier
+ */
+ ClusterId id();
+
+ /**
+ * Returns the number of devices in the cluster.
+ *
+ * @return number of cluster devices
+ */
+ int deviceCount();
+
+ /**
+ * Returns the number of infrastructure links in the cluster.
+ *
+ * @return number of cluster links
+ */
+ int linkCount();
+
+ /**
+ * Returns the cluster root vertex.
+ *
+ * @return cluster root vertex
+ */
+ TopologyVertex root();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEdge.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEdge.java
new file mode 100644
index 00000000..008c8c44
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEdge.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.onlab.graph.Edge;
+import org.onosproject.net.Link;
+
+/**
+ * Represents an edge in the topology graph.
+ */
+public interface TopologyEdge extends Edge<TopologyVertex> {
+
+ /**
+ * Returns the associated infrastructure link.
+ *
+ * @return backing infrastructure link
+ */
+ Link link();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEvent.java
new file mode 100644
index 00000000..10c8dfc3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyEvent.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.event.Event;
+
+import java.util.List;
+
+/**
+ * Describes network topology event.
+ */
+public class TopologyEvent extends AbstractEvent<TopologyEvent.Type, Topology> {
+
+ private final List<Event> reasons;
+
+ /**
+ * Type of topology events.
+ */
+ public enum Type {
+ /**
+ * Signifies that topology has changed.
+ */
+ TOPOLOGY_CHANGED
+ }
+
+ /**
+ * Creates an event of a given type and for the specified topology and the
+ * current time.
+ *
+ * @param type topology event type
+ * @param topology event topology subject
+ * @param reasons list of events that triggered topology change
+ */
+ public TopologyEvent(Type type, Topology topology, List<Event> reasons) {
+ super(type, topology);
+ this.reasons = reasons;
+ }
+
+ /**
+ * Creates an event of a given type and for the specified topology and time.
+ *
+ * @param type link event type
+ * @param topology event topology subject
+ * @param reasons list of events that triggered topology change
+ * @param time occurrence time
+ */
+ public TopologyEvent(Type type, Topology topology, List<Event> reasons,
+ long time) {
+ super(type, topology, time);
+ this.reasons = reasons;
+ }
+
+
+ /**
+ * Returns the list of events that triggered the topology change.
+ *
+ * @return list of events responsible for change in topology; null if
+ * initial topology computation
+ */
+ public List<Event> reasons() {
+ return reasons;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyGraph.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyGraph.java
new file mode 100644
index 00000000..f3565fa4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyGraph.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onlab.graph.Graph;
+
+/**
+ * Represents an immutable topology graph.
+ */
+public interface TopologyGraph extends Graph<TopologyVertex, TopologyEdge> {
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyListener.java
new file mode 100644
index 00000000..625587b0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving network topology related events.
+ */
+public interface TopologyListener extends EventListener<TopologyEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProvider.java
new file mode 100644
index 00000000..f52b798b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Means for injecting topology information into the core.
+ */
+public interface TopologyProvider extends Provider {
+
+ /**
+ * Triggers topology recomputation.
+ */
+ void triggerRecompute();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderRegistry.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderRegistry.java
new file mode 100644
index 00000000..15eeed45
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction of a network topology provider registry.
+ */
+public interface TopologyProviderRegistry extends
+ ProviderRegistry<TopologyProvider, TopologyProviderService> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderService.java
new file mode 100644
index 00000000..742110a2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyProviderService.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.topology;
+
+import org.onosproject.event.Event;
+import org.onosproject.net.provider.ProviderService;
+
+import java.util.List;
+
+/**
+ * Means for injecting topology information into the core.
+ */
+public interface TopologyProviderService extends ProviderService<TopologyProvider> {
+
+ /**
+ * Signals the core that some aspect of the topology has changed.
+ *
+ * @param graphDescription information about the network graph
+ * @param reasons events that triggered topology change
+ */
+ void topologyChanged(GraphDescription graphDescription,
+ List<Event> reasons);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyService.java
new file mode 100644
index 00000000..41eac2c4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyService.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+
+import java.util.Set;
+
+/**
+ * Service for providing network topology information.
+ */
+public interface TopologyService
+ extends ListenerService<TopologyEvent, TopologyListener> {
+
+ /**
+ * Returns the current topology descriptor.
+ *
+ * @return current topology
+ */
+ Topology currentTopology();
+
+ /**
+ * Indicates whether the specified topology is the latest or not.
+ *
+ * @param topology topology descriptor
+ * @return true if the topology is the most recent; false otherwise
+ */
+ boolean isLatest(Topology topology);
+
+ /**
+ * Returns the graph view of the specified topology.
+ *
+ * @param topology topology descriptor
+ * @return topology graph view
+ */
+ TopologyGraph getGraph(Topology topology);
+
+ /**
+ * Returns the set of clusters in the specified topology.
+ *
+ * @param topology topology descriptor
+ * @return set of topology clusters
+ */
+ Set<TopologyCluster> getClusters(Topology topology);
+
+ /**
+ * Returns the cluster with the specified ID.
+ *
+ * @param topology topology descriptor
+ * @param clusterId cluster identifier
+ * @return topology cluster
+ */
+ TopologyCluster getCluster(Topology topology, ClusterId clusterId);
+
+ /**
+ * Returns the set of devices that belong to the specified cluster.
+ *
+ * @param topology topology descriptor
+ * @param cluster topology cluster
+ * @return set of cluster devices
+ */
+ Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster);
+
+ /**
+ * Returns the set of links that form the specified cluster.
+ *
+ * @param topology topology descriptor
+ * @param cluster topology cluster
+ * @return set of cluster links
+ */
+ Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster);
+
+ /**
+ * Returns the set of all shortest paths, precomputed in terms of hop-count,
+ * between the specified source and destination devices.
+ *
+ * @param topology topology descriptor
+ * @param src source device
+ * @param dst destination device
+ * @return set of all shortest paths between the two devices
+ */
+ Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst);
+
+ /**
+ * Returns the set of all shortest paths, computed using the supplied
+ * edge-weight entity, between the specified source and destination devices.
+ *
+ * @param topology topology descriptor
+ * @param src source device
+ * @param dst destination device
+ * @param weight edge-weight entity
+ * @return set of all shortest paths between the two devices
+ */
+ Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+ LinkWeight weight);
+
+ /**
+ * Indicates whether the specified connection point is part of the network
+ * infrastructure or part of network edge.
+ *
+ * @param topology topology descriptor
+ * @param connectPoint connection point
+ * @return true of connection point is in infrastructure; false if edge
+ */
+ boolean isInfrastructure(Topology topology, ConnectPoint connectPoint);
+
+
+ /**
+ * Indicates whether broadcast is allowed for traffic received on the
+ * specified connection point.
+ *
+ * @param topology topology descriptor
+ * @param connectPoint connection point
+ * @return true if broadcast is permissible
+ */
+ boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStore.java
new file mode 100644
index 00000000..983e616e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStore.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+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.ProviderId;
+import org.onosproject.store.Store;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manages inventory of topology snapshots; not intended for direct use.
+ */
+public interface TopologyStore extends Store<TopologyEvent, TopologyStoreDelegate> {
+
+ /**
+ * Returns the current topology snapshot.
+ *
+ * @return current topology descriptor
+ */
+ Topology currentTopology();
+
+ /**
+ * Indicates whether the topology is the latest.
+ *
+ * @param topology topology descriptor
+ * @return true if topology is the most recent one
+ */
+ boolean isLatest(Topology topology);
+
+ /**
+ * Returns the immutable graph view of the current topology.
+ *
+ * @param topology topology descriptor
+ * @return graph view
+ */
+ TopologyGraph getGraph(Topology topology);
+
+ /**
+ * Returns the set of topology SCC clusters.
+ *
+ * @param topology topology descriptor
+ * @return set of clusters
+ */
+ Set<TopologyCluster> getClusters(Topology topology);
+
+ /**
+ * Returns the cluster of the specified topology.
+ *
+ * @param topology topology descriptor
+ * @param clusterId cluster identity
+ * @return topology cluster
+ */
+ TopologyCluster getCluster(Topology topology, ClusterId clusterId);
+
+ /**
+ * Returns the cluster of the specified topology.
+ *
+ * @param topology topology descriptor
+ * @param cluster topology cluster
+ * @return set of cluster links
+ */
+ Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster);
+
+ /**
+ * Returns the cluster of the specified topology.
+ *
+ * @param topology topology descriptor
+ * @param cluster topology cluster
+ * @return set of cluster links
+ */
+ Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster);
+
+ /**
+ * Returns the set of pre-computed shortest paths between src and dest.
+ *
+ * @param topology topology descriptor
+ * @param src source device
+ * @param dst destination device
+ * @return set of shortest paths
+ */
+ Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst);
+
+ /**
+ * Computes and returns the set of shortest paths between src and dest.
+ *
+ * @param topology topology descriptor
+ * @param src source device
+ * @param dst destination device
+ * @param weight link weight function
+ * @return set of shortest paths
+ */
+ Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+ LinkWeight weight);
+
+ /**
+ * Indicates whether the given connect point is part of the network fabric.
+ *
+ * @param topology topology descriptor
+ * @param connectPoint connection point
+ * @return true if infrastructure; false otherwise
+ */
+ boolean isInfrastructure(Topology topology, ConnectPoint connectPoint);
+
+ /**
+ * Indicates whether broadcast is allowed for traffic received on the
+ * given connection point.
+ *
+ * @param topology topology descriptor
+ * @param connectPoint connection point
+ * @return true if broadcast allowed; false otherwise
+ */
+ boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint);
+
+ /**
+ * Generates a new topology snapshot from the specified description.
+ *
+ * @param providerId provider identification
+ * @param graphDescription topology graph description
+ * @param reasons list of events that triggered the update
+ * @return topology update event or null if the description is old
+ */
+ TopologyEvent updateTopology(ProviderId providerId,
+ GraphDescription graphDescription,
+ List<Event> reasons);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStoreDelegate.java
new file mode 100644
index 00000000..c425970b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Topology store delegate abstraction.
+ */
+public interface TopologyStoreDelegate extends StoreDelegate<TopologyEvent> {
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyVertex.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyVertex.java
new file mode 100644
index 00000000..9f37dcb4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/TopologyVertex.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.onlab.graph.Vertex;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Represents a vertex in the topology graph.
+ */
+public interface TopologyVertex extends Vertex {
+
+ /**
+ * Returns the associated infrastructure device identification.
+ *
+ * @return device identifier
+ */
+ DeviceId deviceId();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/package-info.java
new file mode 100644
index 00000000..3cd6ceb8
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/net/topology/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.
+ */
+
+/**
+ * Network topology model &amp; related services API definitions.
+ */
+package org.onosproject.net.topology;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractApiDocRegistrator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractApiDocRegistrator.java
new file mode 100644
index 00000000..7349c69e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractApiDocRegistrator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.rest;
+
+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;
+
+/**
+ * Self-registering REST API provider.
+ */
+@Component(immediate = true, componentAbstract = true)
+public abstract class AbstractApiDocRegistrator {
+
+ protected final ApiDocProvider provider;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApiDocService service;
+
+ /**
+ * Creates registrator for the specified REST API doc provider.
+ *
+ * @param provider REST API provider
+ */
+ protected AbstractApiDocRegistrator(ApiDocProvider provider) {
+ this.provider = provider;
+ }
+
+ @Activate
+ protected void activate() {
+ service.register(provider);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ service.unregister(provider);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractInjectionResource.java b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractInjectionResource.java
new file mode 100644
index 00000000..de182f03
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractInjectionResource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.rest;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Resource for serving semi-static resources.
+ */
+public class AbstractInjectionResource extends AbstractWebResource {
+
+ /**
+ * Returns the index into the supplied string where the end of the
+ * specified pattern is located.
+ *
+ * @param string string to split
+ * @param start index where to start looking for pattern
+ * @param stopPattern optional pattern where to stop
+ * @return index where the split should occur
+ */
+ protected int split(String string, int start, String stopPattern) {
+ int i = stopPattern != null ? string.indexOf(stopPattern, start) : string.length();
+ checkArgument(i >= 0, "Unable to locate pattern %s", stopPattern);
+ return i + (stopPattern != null ? stopPattern.length() : 0);
+ }
+
+ /**
+ * Produces an input stream from the bytes of the specified sub-string.
+ *
+ * @param string source string
+ * @param start index where to start stream
+ * @param end index where to end stream
+ * @return input stream
+ */
+ protected InputStream stream(String string, int start, int end) {
+ return new ByteArrayInputStream(string.substring(start, end).getBytes());
+ }
+
+ /**
+ * Auxiliary enumeration to sequence input streams.
+ */
+ protected class StreamEnumeration implements Enumeration<InputStream> {
+ private final Iterator<InputStream> iterator;
+
+ public StreamEnumeration(List<InputStream> streams) {
+ this.iterator = streams.iterator();
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public InputStream nextElement() {
+ return iterator.next();
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractWebResource.java b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractWebResource.java
new file mode 100644
index 00000000..d3249ba5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/AbstractWebResource.java
@@ -0,0 +1,98 @@
+/*
+ * 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.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.rest.BaseResource;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.JsonCodec;
+
+/**
+ * Abstract REST resource.
+ */
+public class AbstractWebResource extends BaseResource implements CodecContext {
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ public ObjectMapper mapper() {
+ return mapper;
+ }
+
+ /**
+ * Returns the JSON codec for the specified entity class.
+ *
+ * @param entityClass entity class
+ * @param <T> entity type
+ * @return JSON codec
+ */
+ public <T> JsonCodec<T> codec(Class<T> entityClass) {
+ return get(CodecService.class).getCodec(entityClass);
+ }
+
+ /**
+ * Returns JSON object wrapping the array encoding of the specified
+ * collection of items.
+ *
+ * @param codecClass codec item class
+ * @param field field holding the array
+ * @param items collection of items to be encoded into array
+ * @param <T> item type
+ * @return JSON object
+ */
+ protected <T> ObjectNode encodeArray(Class<T> codecClass, String field,
+ Iterable<T> items) {
+ ObjectNode result = mapper().createObjectNode();
+ result.set(field, codec(codecClass).encode(items, this));
+ return result;
+ }
+
+ @Override
+ public <T> T getService(Class<T> serviceClass) {
+ return get(serviceClass);
+ }
+
+ /**
+ * Creates and returns a new child object within the specified parent and
+ * bound to the given key.
+ *
+ * @param parent parent object
+ * @param key key for the new child object
+ * @return child object
+ */
+ public ObjectNode newObject(ObjectNode parent, String key) {
+ ObjectNode node = mapper.createObjectNode();
+ parent.set(key, node);
+ return node;
+ }
+
+ /**
+ * Creates and returns a new child array within the specified parent and
+ * bound to the given key.
+ *
+ * @param parent parent object
+ * @param key key for the new child array
+ * @return child array
+ */
+ public ArrayNode newArray(ObjectNode parent, String key) {
+ ArrayNode node = mapper.createArrayNode();
+ parent.set(key, node);
+ return node;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocProvider.java b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocProvider.java
new file mode 100644
index 00000000..50cac4cd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocProvider.java
@@ -0,0 +1,98 @@
+/*
+ * 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.rest;
+
+import com.google.common.annotations.Beta;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Entity capable of providing REST API documentation resources.
+ */
+@Beta
+public class ApiDocProvider {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String DOCS = "/apidoc/swagger.json";
+ private static final String MODEL = "/apidoc/model.json";
+
+ private final String key;
+ private final String name;
+ private final ClassLoader classLoader;
+
+ /**
+ * Creates a new REST API documentation provider.
+ *
+ * @param key REST API key
+ * @param name REST API name
+ * @param classLoader class loader
+ */
+ public ApiDocProvider(String key, String name, ClassLoader classLoader) {
+ this.key = checkNotNull(key, "Key cannot be null");
+ this.name = checkNotNull(name, "Name cannot be null");
+ this.classLoader = checkNotNull(classLoader, "Class loader cannot be null");
+ }
+
+ /**
+ * Returns the REST API key.
+ *
+ * @return REST API key
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns the REST API name.
+ *
+ * @return REST API name
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns input stream containing Swagger UI compatible JSON.
+ *
+ * @return input stream with Swagger JSON data
+ */
+ public InputStream docs() {
+ return get(DOCS);
+ }
+
+ /**
+ * Returns input stream containing JSON model schema.
+ *
+ * @return input stream with JSON model schema
+ */
+ public InputStream model() {
+ return get(MODEL);
+ }
+
+ private InputStream get(String resource) {
+ InputStream stream = classLoader.getResourceAsStream(resource);
+ if (stream == null) {
+ log.warn("Unable to find JSON resource {}", resource);
+ }
+ return stream;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocService.java
new file mode 100644
index 00000000..f7268532
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/ApiDocService.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.rest;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Set;
+
+/**
+ * Service for registering REST API documentation resources.
+ */
+@Beta
+public interface ApiDocService {
+
+ /**
+ * Registers the specified REST API documentation provider.
+ *
+ * @param provider REST API documentation provider
+ */
+ void register(ApiDocProvider provider);
+
+ /**
+ * Unregisters the specified REST API documentation provider.
+ *
+ * @param provider REST API documentation provider
+ */
+ void unregister(ApiDocProvider provider);
+
+ /**
+ * Returns the set of all registered REST API documentation providers.
+ *
+ * @return set of registered documentation providers
+ */
+ Set<ApiDocProvider> getDocProviders();
+
+ /**
+ * Returns the specified REST API documentation provider with the specified
+ * key.
+ *
+ * @param key REST API key
+ * @return documentation provider
+ */
+ ApiDocProvider getDocProvider(String key);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/rest/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/package-info.java
new file mode 100644
index 00000000..15e5d20d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/rest/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.
+ */
+
+/**
+ * Base abstractions and utilities for developing REST APIs.
+ */
+package org.onosproject.rest; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/security/AppGuard.java b/framework/src/onos/core/api/src/main/java/org/onosproject/security/AppGuard.java
new file mode 100644
index 00000000..800135f4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/security/AppGuard.java
@@ -0,0 +1,38 @@
+/*
+ * 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.security;
+
+
+/**
+ * Aids SM-ONOS to perform API-level permission checking.
+ */
+public final class AppGuard {
+
+ private AppGuard() {
+ }
+
+ /**
+ * Checks if the caller has the required permission only when security-mode is enabled.
+ * @param permission permission to be checked
+ */
+ public static void checkPermission(AppPermission.Type permission) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ System.getSecurityManager().checkPermission(new AppPermission(permission));
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/security/AppPermission.java b/framework/src/onos/core/api/src/main/java/org/onosproject/security/AppPermission.java
new file mode 100644
index 00000000..21a70d2b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/security/AppPermission.java
@@ -0,0 +1,110 @@
+/*
+ * 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.security;
+
+import java.security.BasicPermission;
+
+/**
+ * Implementation of API access permission.
+ */
+public class AppPermission extends BasicPermission {
+
+ public enum Type {
+ APP_READ,
+ APP_EVENT,
+ CONFIG_READ,
+ CONFIG_WRITE,
+ CLUSTER_READ,
+ CLUSTER_WRITE,
+ CLUSTER_EVENT,
+ DEVICE_READ,
+ DEVICE_EVENT,
+ DRIVER_READ,
+ DRIVER_WRITE,
+ FLOWRULE_READ,
+ FLOWRULE_WRITE,
+ FLOWRULE_EVENT,
+ GROUP_READ,
+ GROUP_WRITE,
+ GROUP_EVENT,
+ HOST_READ,
+ HOST_WRITE,
+ HOST_EVENT,
+ INTENT_READ,
+ INTENT_WRITE,
+ INTENT_EVENT,
+ LINK_READ,
+ LINK_WRITE,
+ LINK_EVENT,
+ PACKET_READ,
+ PACKET_WRITE,
+ PACKET_EVENT,
+ STATISTIC_READ,
+ TOPOLOGY_READ,
+ TOPOLOGY_EVENT,
+ TUNNEL_READ,
+ TUNNEL_WRITE,
+ TUNNEL_EVENT,
+ STORAGE_WRITE
+ }
+
+ protected Type type;
+ /**
+ * Creates new application permission using the supplied data.
+ * @param name permission name
+ */
+ public AppPermission(String name) {
+ super(name.toUpperCase(), "");
+ try {
+ type = Type.valueOf(name);
+ } catch (IllegalArgumentException e) {
+ type = null;
+ }
+ }
+
+ /**
+ * Creates new application permission using the supplied data.
+ * @param name permission name
+ * @param actions permission action
+ */
+ public AppPermission(String name, String actions) {
+ super(name.toUpperCase(), actions);
+ try {
+ type = Type.valueOf(name);
+ } catch (IllegalArgumentException e) {
+ type = null;
+ }
+ }
+
+ /**
+ * Crates new application permission using the supplied data.
+ * @param type permission type
+ */
+ public AppPermission(Type type) {
+ super(type.name(), "");
+ this.type = type;
+ }
+
+ /**
+ * Returns type of permission.
+ * @return application permission type
+ */
+ public Type getType() {
+ return this.type;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/security/Permission.java b/framework/src/onos/core/api/src/main/java/org/onosproject/security/Permission.java
new file mode 100644
index 00000000..75d9433f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/security/Permission.java
@@ -0,0 +1,77 @@
+/*
+ * 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.security;
+
+public class Permission {
+
+ protected String classname;
+ protected String name;
+ protected String actions;
+
+ public Permission(String classname, String name, String actions) {
+ this.classname = classname;
+ this.name = name;
+ if (actions == null) {
+ this.actions = "";
+ } else {
+ this.actions = actions;
+ }
+ }
+
+ public Permission(String classname, String name) {
+ this.classname = classname;
+ this.name = name;
+ this.actions = "";
+ }
+
+ public String getClassName() {
+ return classname;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getActions() {
+ return actions;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object thatPerm) {
+ if (this == thatPerm) {
+ return true;
+ }
+
+ if (!(thatPerm instanceof Permission)) {
+ return false;
+ }
+
+ Permission that = (Permission) thatPerm;
+ return (this.classname.equals(that.classname)) && (this.name.equals(that.name))
+ && (this.actions.equals(that.actions));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s, %s, %s)", classname, name, actions);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityAdminService.java
new file mode 100644
index 00000000..16ea94d1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityAdminService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.security;
+
+import org.onosproject.core.ApplicationId;
+
+import java.security.Permission;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Security-Mode ONOS service.
+ */
+public interface SecurityAdminService {
+
+ /**
+ * Returns true if security policy has been enforced to specified application.
+ * @param appId application identifier
+ * @return true if secured.
+ */
+ boolean isSecured(ApplicationId appId);
+
+ /**
+ * Changes SecurityModeState of specified application to REVIEWED.
+ * @param appId application identifier
+ */
+ void review(ApplicationId appId);
+
+ /**
+ * Accepts and enforces security policy to specified application.
+ * @param appId application identifier
+ */
+ void acceptPolicy(ApplicationId appId);
+
+ /**
+ * Register application to SM-ONOS subsystem.
+ * @param appId application identifier
+ */
+ void register(ApplicationId appId);
+
+ /**
+ * Returns sorted developer specified permission Map.
+ * @param appId application identifier
+ * @return Map of list of permissions sorted by permission type
+ */
+ Map<Integer, List<Permission>> getPrintableSpecifiedPermissions(ApplicationId appId);
+
+ /**
+ * Returns sorted granted permission Map.
+ * @param appId application identifier
+ * @return Map of list of permissions sorted by permission type
+ */
+ Map<Integer, List<Permission>> getPrintableGrantedPermissions(ApplicationId appId);
+
+ /**
+ * Returns sorted requested permission Map.
+ * @param appId application identifier
+ * @return Map of list of permissions sorted by permission type
+ */
+ Map<Integer, List<Permission>> getPrintableRequestedPermissions(ApplicationId appId);
+
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityUtil.java b/framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityUtil.java
new file mode 100644
index 00000000..34b4e78a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/security/SecurityUtil.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.security;
+
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
+import org.onosproject.core.ApplicationId;
+
+/**
+ * Utility class to aid Security-Mode ONOS.
+ */
+public final class SecurityUtil {
+
+ protected static ServiceDirectory serviceDirectory = new DefaultServiceDirectory();
+
+ private SecurityUtil() {
+ }
+
+ public static boolean isSecurityModeEnabled() {
+ if (System.getSecurityManager() != null) {
+ try {
+ SecurityAdminService securityService = serviceDirectory.get(SecurityAdminService.class);
+ if (securityService != null) {
+ return true;
+ }
+ } catch (ServiceNotFoundException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public static SecurityAdminService getSecurityService() {
+ if (System.getSecurityManager() != null) {
+ try {
+ SecurityAdminService securityService = serviceDirectory.get(SecurityAdminService.class);
+ if (securityService != null) {
+ return securityService;
+ }
+ } catch (ServiceNotFoundException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public static boolean isAppSecured(ApplicationId appId) {
+ SecurityAdminService service = getSecurityService();
+ if (service != null) {
+ if (!service.isSecured(appId)) {
+ System.out.println("\n*******************************");
+ System.out.println(" SM-ONOS APP WARNING ");
+ System.out.println("*******************************");
+ System.out.println(appId.name() + " has not been secured.");
+ System.out.println("Please review before activating.");
+ return false;
+ }
+ }
+ return true;
+ }
+ public static void register(ApplicationId appId) {
+ SecurityAdminService service = getSecurityService();
+ if (service != null) {
+ service.register(appId);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/security/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/security/package-info.java
new file mode 100644
index 00000000..88c3529d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/security/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.
+ */
+
+/**
+ * Application security constructs.
+ */
+package org.onosproject.security; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/AbstractStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/AbstractStore.java
new file mode 100644
index 00000000..a3005e45
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/AbstractStore.java
@@ -0,0 +1,72 @@
+/*
+ * 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.store;
+
+import java.util.List;
+
+import org.onosproject.event.Event;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of a store.
+ */
+public class AbstractStore<E extends Event, D extends StoreDelegate<E>>
+ implements Store<E, D> {
+
+ protected D delegate;
+
+ @Override
+ public void setDelegate(D delegate) {
+ checkState(this.delegate == null || this.delegate == delegate,
+ "Store delegate already set");
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(D delegate) {
+ if (this.delegate == delegate) {
+ this.delegate = null;
+ }
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return delegate != null;
+ }
+
+ /**
+ * Notifies the delegate with the specified event.
+ *
+ * @param event event to delegate
+ */
+ protected void notifyDelegate(E event) {
+ if (delegate != null) {
+ delegate.notify(event);
+ }
+ }
+
+ /**
+ * Notifies the delegate with the specified list of events.
+ *
+ * @param events list of events to delegate
+ */
+ protected void notifyDelegate(List<E> events) {
+ for (E event: events) {
+ notifyDelegate(event);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/Store.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/Store.java
new file mode 100644
index 00000000..8d5b53c4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/Store.java
@@ -0,0 +1,51 @@
+/*
+ * 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.store;
+
+import org.onosproject.event.Event;
+
+/**
+ * Abstraction of a entity capable of storing and/or distributing information
+ * across a cluster.
+ */
+public interface Store<E extends Event, D extends StoreDelegate<E>> {
+
+ /**
+ * Sets the delegate on the store.
+ *
+ * @param delegate new store delegate
+ * @throws java.lang.IllegalStateException if a delegate is already
+ * currently set on the store and is a different one that
+ */
+ void setDelegate(D delegate);
+
+ /**
+ * Withdraws the delegate from the store.
+ *
+ * @param delegate store delegate to withdraw
+ * @throws java.lang.IllegalArgumentException if the delegate is not
+ * currently set on the store
+ */
+ void unsetDelegate(D delegate);
+
+ /**
+ * Indicates whether the store has a delegate.
+ *
+ * @return true if delegate is set
+ */
+ boolean hasDelegate();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/StoreDelegate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/StoreDelegate.java
new file mode 100644
index 00000000..71ca8cc0
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/StoreDelegate.java
@@ -0,0 +1,33 @@
+/*
+ * 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.store;
+
+import org.onosproject.event.Event;
+
+/**
+ * Entity associated with a store and capable of receiving notifications of
+ * events within the store.
+ */
+public interface StoreDelegate<E extends Event> {
+
+ /**
+ * Notifies the delegate via the specified event.
+ *
+ * @param event store generated event
+ */
+ void notify(E event);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/Timestamp.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/Timestamp.java
new file mode 100644
index 00000000..44238ff2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/Timestamp.java
@@ -0,0 +1,53 @@
+/*
+ * 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.store;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Opaque version structure.
+ * <p>
+ * Classes implementing this interface must also implement
+ * {@link #hashCode()} and {@link #equals(Object)}.
+ */
+public interface Timestamp extends Comparable<Timestamp> {
+
+ @Override
+ int hashCode();
+
+ @Override
+ boolean equals(Object obj);
+
+ /**
+ * Tests if this timestamp is newer than the specified timestamp.
+ *
+ * @param other timestamp to compare against
+ * @return true if this instance is newer
+ */
+ default boolean isNewerThan(Timestamp other) {
+ return this.compareTo(checkNotNull(other)) > 0;
+ }
+
+ /**
+ * Tests if this timestamp is older than the specified timestamp.
+ *
+ * @param other timestamp to compare against
+ * @return true if this instance is older
+ */
+ default boolean isOlderThan(Timestamp other) {
+ return this.compareTo(checkNotNull(other)) < 0;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterCommunicationService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterCommunicationService.java
new file mode 100644
index 00000000..161a8528
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterCommunicationService.java
@@ -0,0 +1,166 @@
+/*
+ * 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.store.cluster.messaging;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Service for assisting communications between controller cluster nodes.
+ */
+public interface ClusterCommunicationService {
+
+ /**
+ * Adds a new subscriber for the specified message subject.
+ *
+ * @param subject message subject
+ * @param subscriber message subscriber
+ * @param executor executor to use for running handler.
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ void addSubscriber(MessageSubject subject, ClusterMessageHandler subscriber, ExecutorService executor);
+
+ /**
+ * Broadcasts a message to all controller nodes.
+ *
+ * @param message message to send
+ * @param subject message subject
+ * @param encoder function for encoding message to byte[]
+ * @param <M> message type
+ */
+ <M> void broadcast(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder);
+
+ /**
+ * Broadcasts a message to all controller nodes including self.
+ *
+ * @param message message to send
+ * @param subject message subject
+ * @param encoder function for encoding message to byte[]
+ * @param <M> message type
+ */
+ <M> void broadcastIncludeSelf(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder);
+
+ /**
+ * Sends a message to the specified controller node.
+ *
+ * @param message message to send
+ * @param subject message subject
+ * @param encoder function for encoding message to byte[]
+ * @param toNodeId destination node identifier
+ * @param <M> message type
+ * @return future that is completed when the message is sent
+ */
+ <M> CompletableFuture<Void> unicast(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder,
+ NodeId toNodeId);
+
+ /**
+ * Multicasts a message to a set of controller nodes.
+ *
+ * @param message message to send
+ * @param subject message subject
+ * @param encoder function for encoding message to byte[]
+ * @param nodeIds recipient node identifiers
+ * @param <M> message type
+ */
+ <M> void multicast(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder,
+ Set<NodeId> nodeIds);
+
+ /**
+ * Sends a message and expects a reply.
+ *
+ * @param message message to send
+ * @param subject message subject
+ * @param encoder function for encoding request to byte[]
+ * @param decoder function for decoding response from byte[]
+ * @param toNodeId recipient node identifier
+ * @param <M> request type
+ * @param <R> reply type
+ * @return reply future
+ */
+ <M, R> CompletableFuture<R> sendAndReceive(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder,
+ Function<byte[], R> decoder,
+ NodeId toNodeId);
+
+ /**
+ * Adds a new subscriber for the specified message subject.
+ *
+ * @param subject message subject
+ * @param decoder decoder for resurrecting incoming message
+ * @param handler handler function that processes the incoming message and produces a reply
+ * @param encoder encoder for serializing reply
+ * @param executor executor to run this handler on
+ * @param <M> incoming message type
+ * @param <R> reply message type
+ */
+ <M, R> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder,
+ Function<M, R> handler,
+ Function<R, byte[]> encoder,
+ Executor executor);
+
+ /**
+ * Adds a new subscriber for the specified message subject.
+ *
+ * @param subject message subject
+ * @param decoder decoder for resurrecting incoming message
+ * @param handler handler function that processes the incoming message and produces a reply
+ * @param encoder encoder for serializing reply
+ * @param <M> incoming message type
+ * @param <R> reply message type
+ */
+ <M, R> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder,
+ Function<M, CompletableFuture<R>> handler,
+ Function<R, byte[]> encoder);
+
+ /**
+ * Adds a new subscriber for the specified message subject.
+ *
+ * @param subject message subject
+ * @param decoder decoder to resurrecting incoming message
+ * @param handler handler for handling message
+ * @param executor executor to run this handler on
+ * @param <M> incoming message type
+ */
+ <M> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder,
+ Consumer<M> handler,
+ Executor executor);
+
+ /**
+ * Removes a subscriber for the specified message subject.
+ *
+ * @param subject message subject
+ */
+ void removeSubscriber(MessageSubject subject);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessage.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessage.java
new file mode 100644
index 00000000..46560e4c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessage.java
@@ -0,0 +1,160 @@
+/*
+ * 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.store.cluster.messaging;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.onlab.util.ByteArraySizeHashPrinter;
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.MoreObjects;
+
+// TODO: Should payload type be ByteBuffer?
+/**
+ * Base message for cluster-wide communications.
+ */
+public class ClusterMessage {
+
+ private final NodeId sender;
+ private final MessageSubject subject;
+ private final byte[] payload;
+ private transient byte[] response;
+
+ /**
+ * Creates a cluster message.
+ *
+ * @param sender message sender
+ * @param subject message subject
+ * @param payload message payload
+ */
+ public ClusterMessage(NodeId sender, MessageSubject subject, byte[] payload) {
+ this.sender = sender;
+ this.subject = subject;
+ this.payload = payload;
+ }
+
+ /**
+ * Returns the id of the controller sending this message.
+ *
+ * @return message sender id.
+ */
+ public NodeId sender() {
+ return sender;
+ }
+
+ /**
+ * Returns the message subject indicator.
+ *
+ * @return message subject
+ */
+ public MessageSubject subject() {
+ return subject;
+ }
+
+ /**
+ * Returns the message payload.
+ *
+ * @return message payload.
+ */
+ public byte[] payload() {
+ return payload;
+ }
+
+ /**
+ * Records the response to be sent to the sender.
+ *
+ * @param data response payload
+ */
+ public void respond(byte[] data) {
+ response = data;
+ }
+
+ /**
+ * Returns the response to be sent to the sender.
+ *
+ * @return response bytes
+ */
+ public byte[] response() {
+ return response;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("sender", sender)
+ .add("subject", subject)
+ .add("payload", ByteArraySizeHashPrinter.of(payload))
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ClusterMessage)) {
+ return false;
+ }
+
+ ClusterMessage that = (ClusterMessage) o;
+
+ return Objects.equals(this.sender, that.sender) &&
+ Objects.equals(this.subject, that.subject) &&
+ Arrays.equals(this.payload, that.payload);
+ }
+
+ /**
+ * Serializes this instance.
+ * @return bytes
+ */
+ public byte[] getBytes() {
+ byte[] senderBytes = sender.toString().getBytes(Charsets.UTF_8);
+ byte[] subjectBytes = subject.value().getBytes(Charsets.UTF_8);
+ int capacity = 12 + senderBytes.length + subjectBytes.length + payload.length;
+ ByteBuffer buffer = ByteBuffer.allocate(capacity);
+ buffer.putInt(senderBytes.length);
+ buffer.put(senderBytes);
+ buffer.putInt(subjectBytes.length);
+ buffer.put(subjectBytes);
+ buffer.putInt(payload.length);
+ buffer.put(payload);
+ return buffer.array();
+ }
+
+ /**
+ * Decodes a new ClusterMessage from raw bytes.
+ * @param bytes raw bytes
+ * @return cluster message
+ */
+ public static ClusterMessage fromBytes(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ byte[] senderBytes = new byte[buffer.getInt()];
+ buffer.get(senderBytes);
+ byte[] subjectBytes = new byte[buffer.getInt()];
+ buffer.get(subjectBytes);
+ byte[] payloadBytes = new byte[buffer.getInt()];
+ buffer.get(payloadBytes);
+
+ return new ClusterMessage(new NodeId(new String(senderBytes, Charsets.UTF_8)),
+ new MessageSubject(new String(subjectBytes, Charsets.UTF_8)),
+ payloadBytes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(sender, subject, payload);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessageHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessageHandler.java
new file mode 100644
index 00000000..ce770a81
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/ClusterMessageHandler.java
@@ -0,0 +1,28 @@
+/*
+ * 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.store.cluster.messaging;
+
+/**
+ * Interface for handling cluster messages.
+ */
+public interface ClusterMessageHandler {
+
+ /**
+ * Handles/Processes the cluster message.
+ * @param message cluster message.
+ */
+ void handle(ClusterMessage message);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/Endpoint.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/Endpoint.java
new file mode 100644
index 00000000..2ac50dfd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/Endpoint.java
@@ -0,0 +1,75 @@
+/*
+ * 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.store.cluster.messaging;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import org.onlab.packet.IpAddress;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Representation of a TCP/UDP communication end point.
+ */
+public final class Endpoint {
+
+ private final int port;
+ private final IpAddress ip;
+
+ public Endpoint(IpAddress host, int port) {
+ this.ip = checkNotNull(host);
+ this.port = port;
+ }
+
+ public IpAddress host() {
+ return ip;
+ }
+
+ public int port() {
+ return port;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("ip", ip)
+ .add("port", port)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ip, port);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Endpoint that = (Endpoint) obj;
+ return Objects.equals(this.port, that.port) &&
+ Objects.equals(this.ip, that.ip);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessageSubject.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessageSubject.java
new file mode 100644
index 00000000..8d5e313d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessageSubject.java
@@ -0,0 +1,68 @@
+/*
+ * 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.store.cluster.messaging;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+/**
+ * Representation of a message subject.
+ * Cluster messages have associated subjects that dictate how they get handled
+ * on the receiving side.
+ */
+public final class MessageSubject {
+
+ private final String value;
+
+ public MessageSubject(String value) {
+ this.value = checkNotNull(value);
+ }
+
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MessageSubject that = (MessageSubject) obj;
+ return Objects.equals(this.value, that.value);
+ }
+
+ // for serializer
+ protected MessageSubject() {
+ this.value = "";
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessagingService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessagingService.java
new file mode 100644
index 00000000..6ccd4835
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/MessagingService.java
@@ -0,0 +1,75 @@
+/*
+ * 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.store.cluster.messaging;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Interface for low level messaging primitives.
+ */
+public interface MessagingService {
+
+ /**
+ * Sends a message asynchronously to the specified communication end point.
+ * The message is specified using the type and payload.
+ * @param ep end point to send the message to.
+ * @param type type of message.
+ * @param payload message payload bytes.
+ * @return future that is completed when the message is sent
+ */
+ CompletableFuture<Void> sendAsync(Endpoint ep, String type, byte[] payload);
+
+ /**
+ * Sends a message synchronously and waits for a response.
+ * @param ep end point to send the message to.
+ * @param type type of message.
+ * @param payload message payload.
+ * @return a response future
+ */
+ CompletableFuture<byte[]> sendAndReceive(Endpoint ep, String type, byte[] payload);
+
+ /**
+ * Registers a new message handler for message type.
+ * @param type message type.
+ * @param handler message handler
+ * @param executor executor to use for running message handler logic.
+ */
+ void registerHandler(String type, Consumer<byte[]> handler, Executor executor);
+
+ /**
+ * Registers a new message handler for message type.
+ * @param type message type.
+ * @param handler message handler
+ * @param executor executor to use for running message handler logic.
+ */
+ void registerHandler(String type, Function<byte[], byte[]> handler, Executor executor);
+
+ /**
+ * Registers a new message handler for message type.
+ * @param type message type.
+ * @param handler message handler
+ */
+ void registerHandler(String type, Function<byte[], CompletableFuture<byte[]>> handler);
+
+ /**
+ * Unregister current handler, if one exists for message type.
+ * @param type message type
+ */
+ void unregisterHandler(String type);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/package-info.java
new file mode 100644
index 00000000..582c50ed
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/cluster/messaging/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.
+ */
+
+/**
+ * Cluster messaging APIs for the use by the various distributed stores.
+ */
+package org.onosproject.store.cluster.messaging;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/package-info.java
new file mode 100644
index 00000000..b6269ea5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Abstractions for creating and interacting with distributed stores.
+ */
+package org.onosproject.store;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncAtomicCounter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncAtomicCounter.java
new file mode 100644
index 00000000..a879cc59
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncAtomicCounter.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.store.service;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * An async atomic counter dispenses monotonically increasing values.
+ */
+public interface AsyncAtomicCounter {
+
+ /**
+ * Atomically increment by one the current value.
+ *
+ * @return updated value
+ */
+ CompletableFuture<Long> incrementAndGet();
+
+ /**
+ * Atomically increment by one the current value.
+ *
+ * @return previous value
+ */
+ CompletableFuture<Long> getAndIncrement();
+
+ /**
+ * Atomically adds the given value to the current value.
+ *
+ * @param delta the value to add
+ * @return previous value
+ */
+ CompletableFuture<Long> getAndAdd(long delta);
+
+ /**
+ * Atomically adds the given value to the current value.
+ *
+ * @param delta the value to add
+ * @return updated value
+ */
+ CompletableFuture<Long> addAndGet(long delta);
+
+ /**
+ * Returns the current value of the counter without modifying it.
+ *
+ * @return current value
+ */
+ CompletableFuture<Long> get();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
new file mode 100644
index 00000000..fee8cfa6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
@@ -0,0 +1,283 @@
+/*
+ * 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.store.service;
+
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * A distributed, strongly consistent map whose methods are all executed asynchronously.
+ * <p>
+ * This map offers strong read-after-update (where update == create/update/delete)
+ * consistency. All operations to the map are serialized and applied in a consistent
+ * manner.
+ * <p>
+ * The stronger consistency comes at the expense of availability in
+ * the event of a network partition. A network partition can be either due to
+ * a temporary disruption in network connectivity between participating nodes
+ * or due to a node being temporarily down.
+ * </p><p>
+ * All values stored in this map are versioned and the API supports optimistic
+ * concurrency by allowing conditional updates that take into consideration
+ * the version or value that was previously read.
+ * </p><p>
+ * This map does not allow null values. All methods can throw a ConsistentMapException
+ * (which extends RuntimeException) to indicate failures.
+ *
+ */
+public interface AsyncConsistentMap<K, V> {
+
+ /**
+ * Returns the number of entries in the map.
+ *
+ * @return a future for map size.
+ */
+ CompletableFuture<Integer> size();
+
+ /**
+ * Returns true if the map is empty.
+ *
+ * @return a future whose value will be true if map has no entries, false otherwise.
+ */
+ CompletableFuture<Boolean> isEmpty();
+
+ /**
+ * Returns true if this map contains a mapping for the specified key.
+ *
+ * @param key key
+ * @return a future whose value will be true if map contains key, false otherwise.
+ */
+ CompletableFuture<Boolean> containsKey(K key);
+
+ /**
+ * Returns true if this map contains the specified value.
+ *
+ * @param value value
+ * @return a future whose value will be true if map contains value, false otherwise.
+ */
+ CompletableFuture<Boolean> containsValue(V value);
+
+ /**
+ * Returns the value (and version) to which the specified key is mapped, or null if this
+ * map contains no mapping for the key.
+ *
+ * @param key the key whose associated value (and version) is to be returned
+ * @return a future value (and version) to which the specified key is mapped, or null if
+ * this map contains no mapping for the key
+ */
+ CompletableFuture<Versioned<V>> get(K key);
+
+ /**
+ * If the specified key is not already associated with a value (or is mapped to null),
+ * attempts to compute its value using the given mapping function and enters it into
+ * this map unless null.
+ * If a conflicting concurrent modification attempt is detected, the returned future
+ * will be completed exceptionally with ConsistentMapException.ConcurrentModification.
+ * @param key key with which the specified value is to be associated
+ * @param mappingFunction the function to compute a value
+ * @return the current (existing or computed) value associated with the specified key,
+ * or null if the computed value is null
+ */
+ CompletableFuture<Versioned<V>> computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction);
+
+ /**
+ * If the value for the specified key is present and non-null, attempts to compute a new
+ * mapping given the key and its current mapped value.
+ * If the computed value is null, the current mapping will be removed from the map.
+ * If a conflicting concurrent modification attempt is detected, the returned future
+ * will be completed exceptionally with ConsistentMapException.ConcurrentModification.
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if computed value is null
+ */
+ CompletableFuture<Versioned<V>> computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction);
+
+ /**
+ * Attempts to compute a mapping for the specified key and its current mapped value (or
+ * null if there is no current mapping).
+ * If the computed value is null, the current mapping (if one exists) will be removed from the map.
+ * If a conflicting concurrent modification attempt is detected, the returned future
+ * will be completed exceptionally with ConsistentMapException.ConcurrentModification.
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if computed value is null
+ */
+ CompletableFuture<Versioned<V>> compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction);
+
+ /**
+ * If the value for the specified key satisfies a condition, attempts to compute a new
+ * mapping given the key and its current mapped value.
+ * If the computed value is null, the current mapping will be removed from the map.
+ * If a conflicting concurrent modification attempt is detected, the returned future
+ * will be completed exceptionally with ConsistentMapException.ConcurrentModification.
+ * @param key key with which the specified value is to be associated
+ * @param condition condition that should evaluate to true for the computation to proceed
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or the old value if condition evaluates to false
+ */
+ CompletableFuture<Versioned<V>> computeIf(K key,
+ Predicate<? super V> condition,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction);
+
+ /**
+ * Associates the specified value with the specified key in this map (optional operation).
+ * If the map previously contained a mapping for the key, the old value is replaced by the
+ * specified value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value (and version) associated with key, or null if there was
+ * no mapping for key.
+ */
+ CompletableFuture<Versioned<V>> put(K key, V value);
+
+ /**
+ * Associates the specified value with the specified key in this map (optional operation).
+ * If the map previously contained a mapping for the key, the old value is replaced by the
+ * specified value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return new value.
+ */
+ CompletableFuture<Versioned<V>> putAndGet(K key, V value);
+
+ /**
+ * Removes the mapping for a key from this map if it is present (optional operation).
+ *
+ * @param key key whose value is to be removed from the map
+ * @return the value (and version) to which this map previously associated the key,
+ * or null if the map contained no mapping for the key.
+ */
+ CompletableFuture<Versioned<V>> remove(K key);
+
+ /**
+ * Removes all of the mappings from this map (optional operation).
+ * The map will be empty after this call returns.
+ * @return future that will be successfully completed when the map is cleared
+ */
+ CompletableFuture<Void> clear();
+
+ /**
+ * Returns a Set view of the keys contained in this map.
+ * This method differs from the behavior of java.util.Map.keySet() in that
+ * what is returned is a unmodifiable snapshot view of the keys in the ConsistentMap.
+ * Attempts to modify the returned set, whether direct or via its iterator,
+ * result in an UnsupportedOperationException.
+ *
+ * @return a set of the keys contained in this map
+ */
+ CompletableFuture<Set<K>> keySet();
+
+ /**
+ * Returns the collection of values (and associated versions) contained in this map.
+ * This method differs from the behavior of java.util.Map.values() in that
+ * what is returned is a unmodifiable snapshot view of the values in the ConsistentMap.
+ * Attempts to modify the returned collection, whether direct or via its iterator,
+ * result in an UnsupportedOperationException.
+ *
+ * @return a collection of the values (and associated versions) contained in this map
+ */
+ CompletableFuture<Collection<Versioned<V>>> values();
+
+ /**
+ * Returns the set of entries contained in this map.
+ * This method differs from the behavior of java.util.Map.entrySet() in that
+ * what is returned is a unmodifiable snapshot view of the entries in the ConsistentMap.
+ * Attempts to modify the returned set, whether direct or via its iterator,
+ * result in an UnsupportedOperationException.
+ *
+ * @return set of entries contained in this map.
+ */
+ CompletableFuture<Set<Entry<K, Versioned<V>>>> entrySet();
+
+ /**
+ * If the specified key is not already associated with a value
+ * associates it with the given value and returns null, else returns the current value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key or null
+ * if key does not already mapped to a value.
+ */
+ CompletableFuture<Versioned<V>> putIfAbsent(K key, V value);
+
+ /**
+ * Removes the entry for the specified key only if it is currently
+ * mapped to the specified value.
+ *
+ * @param key key with which the specified value is associated
+ * @param value value expected to be associated with the specified key
+ * @return true if the value was removed
+ */
+ CompletableFuture<Boolean> remove(K key, V value);
+
+ /**
+ * Removes the entry for the specified key only if its current
+ * version in the map is equal to the specified version.
+ *
+ * @param key key with which the specified version is associated
+ * @param version version expected to be associated with the specified key
+ * @return true if the value was removed
+ */
+ CompletableFuture<Boolean> remove(K key, long version);
+
+ /**
+ * Replaces the entry for the specified key only if currently mapped
+ * to the specified value.
+ *
+ * @param key key with which the specified value is associated
+ * @param oldValue value expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return true if the value was replaced
+ */
+ CompletableFuture<Boolean> replace(K key, V oldValue, V newValue);
+
+ /**
+ * Replaces the entry for the specified key only if it is currently mapped to the
+ * specified version.
+ *
+ * @param key key key with which the specified value is associated
+ * @param oldVersion version expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return true if the value was replaced
+ */
+ CompletableFuture<Boolean> replace(K key, long oldVersion, V newValue);
+
+ /**
+ * Registers the specified listener to be notified whenever the map is updated.
+ *
+ * @param listener listener to notify about map events
+ */
+ void addListener(MapEventListener<K, V> listener);
+
+ /**
+ * Unregisters the specified listener such that it will no longer
+ * receive map change notifications.
+ *
+ * @param listener listener to unregister
+ */
+ void removeListener(MapEventListener<K, V> listener);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounter.java
new file mode 100644
index 00000000..f620e082
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.store.service;
+
+/**
+ * Distributed version of java.util.concurrent.atomic.AtomicLong.
+ */
+public interface AtomicCounter {
+
+ /**
+ * Atomically increment by one the current value.
+ *
+ * @return updated value
+ */
+ long incrementAndGet();
+
+ /**
+ * Atomically increment by one the current value.
+ *
+ * @return previous value
+ */
+ long getAndIncrement();
+
+ /**
+ * Atomically adds the given value to the current value.
+ *
+ * @param delta the value to add
+ * @return previous value
+ */
+ long getAndAdd(long delta);
+
+ /**
+ * Atomically adds the given value to the current value.
+ *
+ * @param delta the value to add
+ * @return updated value
+ */
+ long addAndGet(long delta);
+
+ /**
+ * Returns the current value of the counter without modifying it.
+ *
+ * @return current value
+ */
+ long get();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounterBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounterBuilder.java
new file mode 100644
index 00000000..41a19f0d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicCounterBuilder.java
@@ -0,0 +1,75 @@
+/*
+ * 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.store.service;
+
+/**
+ * Builder for AtomicCounter.
+ */
+public interface AtomicCounterBuilder {
+
+ /**
+ * Sets the name for the atomic counter.
+ * <p>
+ * Each atomic counter is identified by a unique name.
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param name name of the atomic counter
+ * @return this AtomicCounterBuilder
+ */
+ AtomicCounterBuilder withName(String name);
+
+ /**
+ * Creates this counter on the partition that spans the entire cluster.
+ * <p>
+ * When partitioning is disabled, the counter state will be
+ * ephemeral and does not survive a full cluster restart.
+ * </p>
+ * <p>
+ * Note: By default partitions are enabled.
+ * </p>
+ * @return this AtomicCounterBuilder
+ */
+ AtomicCounterBuilder withPartitionsDisabled();
+
+ /**
+ * Instantiates Metering service to gather usage and performance metrics.
+ * By default, usage data will be stored.
+ *
+ * @return this AtomicCounterBuilder
+ */
+ AtomicCounterBuilder withMeteringDisabled();
+
+ /**
+ * Builds a AtomicCounter based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new AtomicCounter
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ AtomicCounter build();
+
+ /**
+ * Builds a AsyncAtomicCounter based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new AsyncAtomicCounter
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ AsyncAtomicCounter buildAsyncCounter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValue.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValue.java
new file mode 100644
index 00000000..dfa0fb3c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValue.java
@@ -0,0 +1,69 @@
+/*
+ * 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.store.service;
+
+/**
+ * Distributed version of java.util.concurrent.atomic.AtomicReference.
+ *
+ * @param <V> value type
+ */
+public interface AtomicValue<V> {
+
+ /**
+ * Atomically sets the value to the given updated value if the current value is equal to the expected value.
+ * <p>
+ * IMPORTANT: Equality is based on the equality of the serialized byte[] representations.
+ * <p>
+ * @param expect the expected value
+ * @param update the new value
+ * @return true if successful. false return indicates that the actual value was not equal to the expected value.
+ */
+ boolean compareAndSet(V expect, V update);
+
+ /**
+ * Gets the current value.
+ * @return current value
+ */
+ V get();
+
+ /**
+ * Atomically sets to the given value and returns the old value.
+ * @param value the new value
+ * @return previous value
+ */
+ V getAndSet(V value);
+
+ /**
+ * Sets to the given value.
+ * @param value new value
+ */
+ void set(V value);
+
+ /**
+ * Registers the specified listener to be notified whenever the atomic value is updated.
+ *
+ * @param listener listener to notify about events
+ */
+ void addListener(AtomicValueEventListener<V> listener);
+
+ /**
+ * Unregisters the specified listener such that it will no longer
+ * receive atomic value update notifications.
+ *
+ * @param listener listener to unregister
+ */
+ void removeListener(AtomicValueEventListener<V> listener);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueBuilder.java
new file mode 100644
index 00000000..3478ce00
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueBuilder.java
@@ -0,0 +1,78 @@
+/*
+ * 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.store.service;
+
+/**
+ * Builder for constructing new AtomicValue instances.
+ *
+ * @param <V> atomic value type
+ */
+public interface AtomicValueBuilder<V> {
+ /**
+ * Sets the name for the atomic value.
+ * <p>
+ * Each atomic value is identified by a unique name.
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param name name of the atomic value
+ * @return this AtomicValueBuilder for method chaining
+ */
+ AtomicValueBuilder<V> withName(String name);
+
+ /**
+ * Sets a serializer that can be used to serialize the value.
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param serializer serializer
+ * @return this AtomicValueBuilder for method chaining
+ */
+ AtomicValueBuilder<V> withSerializer(Serializer serializer);
+
+ /**
+ * Creates this atomic value on the partition that spans the entire cluster.
+ * <p>
+ * When partitioning is disabled, the value state will be
+ * ephemeral and does not survive a full cluster restart.
+ * </p>
+ * <p>
+ * Note: By default partitions are enabled.
+ * </p>
+ * @return this AtomicValueBuilder for method chaining
+ */
+ AtomicValueBuilder<V> withPartitionsDisabled();
+
+ /**
+ * Instantiates Metering service to gather usage and performance metrics.
+ * By default, usage data will be stored.
+ *
+ * @return this AtomicValueBuilder for method chaining
+ */
+ AtomicValueBuilder<V> withMeteringDisabled();
+
+ /**
+ * Builds a AtomicValue based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new AtomicValue
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ AtomicValue<V> build();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEvent.java
new file mode 100644
index 00000000..1bce1365
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEvent.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.store.service;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Representation of a AtomicValue update notification.
+ *
+ * @param <V> atomic value type
+ */
+public final class AtomicValueEvent<V> {
+
+ /**
+ * AtomicValueEvent type.
+ */
+ public enum Type {
+
+ /**
+ * Value was updated.
+ */
+ UPDATE,
+ }
+
+ private final String name;
+ private final Type type;
+ private final V value;
+
+ /**
+ * Creates a new event object.
+ *
+ * @param name AtomicValue name
+ * @param type the type of the event
+ * @param value the new value
+ */
+ public AtomicValueEvent(String name, Type type, V value) {
+ this.name = name;
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * Returns the AtomicValue name.
+ *
+ * @return name of atomic value
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return the type of the event
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the new updated value.
+ *
+ * @return the value
+ */
+ public V value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof AtomicValueEvent)) {
+ return false;
+ }
+
+ AtomicValueEvent that = (AtomicValueEvent) o;
+ return Objects.equals(this.name, that.name) &&
+ Objects.equals(this.type, that.type) &&
+ Objects.equals(this.value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, value);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .add("type", type)
+ .add("value", value)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEventListener.java
new file mode 100644
index 00000000..b29d903b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/AtomicValueEventListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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.store.service;
+
+/**
+ * Listener to be notified about updates to a AtomicValue.
+ */
+public interface AtomicValueEventListener<V> {
+ /**
+ * Reacts to the specified event.
+ *
+ * @param event the event
+ */
+ void event(AtomicValueEvent<V> event);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMap.java
new file mode 100644
index 00000000..289da202
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMap.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.store.service;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * A distributed, strongly consistent key-value map.
+ * <p>
+ * This map offers strong read-after-update (where update == create/update/delete)
+ * consistency. All operations to the map are serialized and applied in a consistent
+ * manner.
+ * <p>
+ * The stronger consistency comes at the expense of availability in
+ * the event of a network partition. A network partition can be either due to
+ * a temporary disruption in network connectivity between participating nodes
+ * or due to a node being temporarily down.
+ * </p><p>
+ * All values stored in this map are versioned and the API supports optimistic
+ * concurrency by allowing conditional updates that take into consideration
+ * the version or value that was previously read.
+ * </p><p>
+ * This map does not allow null values. All methods can throw a ConsistentMapException
+ * (which extends RuntimeException) to indicate failures.
+ *
+ */
+public interface ConsistentMap<K, V> {
+
+ /**
+ * Returns the number of entries in the map.
+ *
+ * @return map size.
+ */
+ int size();
+
+ /**
+ * Returns true if the map is empty.
+ *
+ * @return true if map has no entries, false otherwise
+ */
+ boolean isEmpty();
+
+ /**
+ * Returns true if this map contains a mapping for the specified key.
+ *
+ * @param key key
+ * @return true if map contains key, false otherwise
+ */
+ boolean containsKey(K key);
+
+ /**
+ * Returns true if this map contains the specified value.
+ *
+ * @param value value
+ * @return true if map contains value, false otherwise.
+ */
+ boolean containsValue(V value);
+
+ /**
+ * Returns the value (and version) to which the specified key is mapped, or null if this
+ * map contains no mapping for the key.
+ *
+ * @param key the key whose associated value (and version) is to be returned
+ * @return the value (and version) to which the specified key is mapped, or null if
+ * this map contains no mapping for the key
+ */
+ Versioned<V> get(K key);
+
+ /**
+ * If the specified key is not already associated with a value (or is mapped to null),
+ * attempts to compute its value using the given mapping function and enters it into
+ * this map unless null.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param mappingFunction the function to compute a value
+ * @return the current (existing or computed) value associated with the specified key,
+ * or null if the computed value is null. Method throws {@code ConsistentMapException.ConcurrentModification}
+ * if a concurrent modification of map is detected
+ */
+ Versioned<V> computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction);
+
+ /**
+ * Attempts to compute a mapping for the specified key and its current mapped value (or
+ * null if there is no current mapping).
+ * If the computed value is null, the current mapping will be removed from the map.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if none.
+ * This method throws {@code ConsistentMapException.ConcurrentModification}
+ * if a concurrent modification of map is detected
+ */
+ Versioned<V> compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction);
+
+ /**
+ * If the value for the specified key is present and non-null, attempts to compute a new
+ * mapping given the key and its current mapped value.
+ * If the computed value is null, the current mapping will be removed from the map.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if none.
+ * This method throws {@code ConsistentMapException.ConcurrentModification}
+ * if a concurrent modification of map is detected
+ */
+ Versioned<V> computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction);
+
+ /**
+ * If the value for the specified key satisfies a condition, attempts to compute a new
+ * mapping given the key and its current mapped value.
+ * If the computed value is null, the current mapping will be removed from the map.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param condition condition that should evaluate to true for the computation to proceed
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or the old value if condition evaluates to false.
+ * This method throws {@code ConsistentMapException.ConcurrentModification} if a concurrent
+ * modification of map is detected
+ */
+ Versioned<V> computeIf(K key,
+ Predicate<? super V> condition,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction);
+
+ /**
+ * Associates the specified value with the specified key in this map (optional operation).
+ * If the map previously contained a mapping for the key, the old value is replaced by the
+ * specified value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value (and version) associated with key, or null if there was
+ * no mapping for key.
+ */
+ Versioned<V> put(K key, V value);
+
+ /**
+ * Associates the specified value with the specified key in this map (optional operation).
+ * If the map previously contained a mapping for the key, the old value is replaced by the
+ * specified value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return new value.
+ */
+ Versioned<V> putAndGet(K key, V value);
+
+ /**
+ * Removes the mapping for a key from this map if it is present (optional operation).
+ *
+ * @param key key whose value is to be removed from the map
+ * @return the value (and version) to which this map previously associated the key,
+ * or null if the map contained no mapping for the key.
+ */
+ Versioned<V> remove(K key);
+
+ /**
+ * Removes all of the mappings from this map (optional operation).
+ * The map will be empty after this call returns.
+ */
+ void clear();
+
+ /**
+ * Returns a Set view of the keys contained in this map.
+ * This method differs from the behavior of java.util.Map.keySet() in that
+ * what is returned is a unmodifiable snapshot view of the keys in the ConsistentMap.
+ * Attempts to modify the returned set, whether direct or via its iterator,
+ * result in an UnsupportedOperationException.
+ *
+ * @return a set of the keys contained in this map
+ */
+ Set<K> keySet();
+
+ /**
+ * Returns the collection of values (and associated versions) contained in this map.
+ * This method differs from the behavior of java.util.Map.values() in that
+ * what is returned is a unmodifiable snapshot view of the values in the ConsistentMap.
+ * Attempts to modify the returned collection, whether direct or via its iterator,
+ * result in an UnsupportedOperationException.
+ *
+ * @return a collection of the values (and associated versions) contained in this map
+ */
+ Collection<Versioned<V>> values();
+
+ /**
+ * Returns the set of entries contained in this map.
+ * This method differs from the behavior of java.util.Map.entrySet() in that
+ * what is returned is a unmodifiable snapshot view of the entries in the ConsistentMap.
+ * Attempts to modify the returned set, whether direct or via its iterator,
+ * result in an UnsupportedOperationException.
+ *
+ * @return set of entries contained in this map.
+ */
+ Set<Entry<K, Versioned<V>>> entrySet();
+
+ /**
+ * If the specified key is not already associated with a value
+ * associates it with the given value and returns null, else returns the current value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key or null
+ * if key does not already mapped to a value.
+ */
+ Versioned<V> putIfAbsent(K key, V value);
+
+ /**
+ * Removes the entry for the specified key only if it is currently
+ * mapped to the specified value.
+ *
+ * @param key key with which the specified value is associated
+ * @param value value expected to be associated with the specified key
+ * @return true if the value was removed
+ */
+ boolean remove(K key, V value);
+
+ /**
+ * Removes the entry for the specified key only if its current
+ * version in the map is equal to the specified version.
+ *
+ * @param key key with which the specified version is associated
+ * @param version version expected to be associated with the specified key
+ * @return true if the value was removed
+ */
+ boolean remove(K key, long version);
+
+ /**
+ * Replaces the entry for the specified key only if currently mapped
+ * to the specified value.
+ *
+ * @param key key with which the specified value is associated
+ * @param oldValue value expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return true if the value was replaced
+ */
+ boolean replace(K key, V oldValue, V newValue);
+
+ /**
+ * Replaces the entry for the specified key only if it is currently mapped to the
+ * specified version.
+ *
+ * @param key key key with which the specified value is associated
+ * @param oldVersion version expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return true if the value was replaced
+ */
+ boolean replace(K key, long oldVersion, V newValue);
+
+ /**
+ * Registers the specified listener to be notified whenever the map is updated.
+ *
+ * @param listener listener to notify about map events
+ */
+ void addListener(MapEventListener<K, V> listener);
+
+ /**
+ * Unregisters the specified listener such that it will no longer
+ * receive map change notifications.
+ *
+ * @param listener listener to unregister
+ */
+ void removeListener(MapEventListener<K, V> listener);
+
+ /**
+ * Returns a java.util.Map instance backed by this ConsistentMap.
+ * @return java.util.Map
+ */
+ Map<K, V> asJavaMap();
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapBuilder.java
new file mode 100644
index 00000000..847adaf6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapBuilder.java
@@ -0,0 +1,143 @@
+/*
+ * 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.store.service;
+
+import org.onosproject.core.ApplicationId;
+
+/**
+ * Builder for consistent maps.
+ *
+ * @param <K> type for map key
+ * @param <V> type for map value
+ */
+public interface ConsistentMapBuilder<K, V> {
+
+ /**
+ * Sets the name of the map.
+ * <p>
+ * Each consistent map is identified by a unique map name.
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param name name of the consistent map
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withName(String name);
+
+ /**
+ * Sets the owner applicationId for the map.
+ * <p>
+ * Note: If {@code purgeOnUninstall} option is enabled, applicationId
+ * must be specified.
+ * </p>
+ *
+ * @param id applicationId owning the consistent map
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withApplicationId(ApplicationId id);
+
+ /**
+ * Sets a serializer that can be used to serialize
+ * both the keys and values inserted into the map. The serializer
+ * builder should be pre-populated with any classes that will be
+ * put into the map.
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param serializer serializer
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withSerializer(Serializer serializer);
+
+ /**
+ * Disables distribution of map entries across multiple database partitions.
+ * <p>
+ * When partitioning is disabled, the returned map will have a single partition
+ * that spans the entire cluster. Furthermore, the changes made to the map are
+ * ephemeral and do not survive a full cluster restart.
+ * </p>
+ * <p>
+ * Disabling partitions is more appropriate when the returned map is used for
+ * coordination activities such as leader election and not for long term data persistence.
+ * </p>
+ * <p>
+ * Note: By default partitions are enabled and entries in the map are durable.
+ * </p>
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withPartitionsDisabled();
+
+ /**
+ * Disables map updates.
+ * <p>
+ * Attempt to update the built map will throw {@code UnsupportedOperationException}.
+ *
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withUpdatesDisabled();
+
+ /**
+ * Purges map contents when the application owning the map is uninstalled.
+ * <p>
+ * When this option is enabled, the caller must provide a applicationId via
+ * the {@code withAppliationId} builder method.
+ * <p>
+ * By default map entries will NOT be purged when owning application is uninstalled.
+ *
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withPurgeOnUninstall();
+
+ /**
+ * Instantiates Metering service to gather usage and performance metrics.
+ * By default, usage data will be stored.
+ *
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withMeteringDisabled();
+
+ /**
+ * Provides weak consistency for map gets.
+ * <p>
+ * While this can lead to improved read performance, it can also make the behavior
+ * heard to reason. Only turn this on if you know what you are doing. By default
+ * reads are strongly consistent.
+ *
+ * @return this ConsistentMapBuilder
+ */
+ ConsistentMapBuilder<K, V> withRelaxedReadConsistency();
+
+ /**
+ * Builds an consistent map based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new consistent map
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ ConsistentMap<K, V> build();
+
+ /**
+ * Builds an async consistent map based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new async consistent map
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ AsyncConsistentMap<K, V> buildAsyncMap();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapException.java
new file mode 100644
index 00000000..94ed649f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/ConsistentMapException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.store.service;
+
+/**
+ * Top level exception for ConsistentMap failures.
+ */
+@SuppressWarnings("serial")
+public class ConsistentMapException extends StorageException {
+ public ConsistentMapException() {
+ }
+
+ public ConsistentMapException(Throwable t) {
+ super(t);
+ }
+
+ /**
+ * ConsistentMap operation timeout.
+ */
+ public static class Timeout extends ConsistentMapException {
+ }
+
+ /**
+ * ConsistentMap update conflicts with an in flight transaction.
+ */
+ public static class ConcurrentModification extends ConsistentMapException {
+ }
+
+ /**
+ * ConsistentMap operation interrupted.
+ */
+ public static class Interrupted extends ConsistentMapException {
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DatabaseUpdate.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DatabaseUpdate.java
new file mode 100644
index 00000000..8cac5968
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DatabaseUpdate.java
@@ -0,0 +1,220 @@
+/*
+ * 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.store.service;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Database update operation.
+ *
+ */
+public final class DatabaseUpdate {
+
+ /**
+ * Type of database update operation.
+ */
+ public static enum Type {
+ /**
+ * Insert/Update entry without any checks.
+ */
+ PUT,
+ /**
+ * Insert an entry iff there is no existing entry for that key.
+ */
+ PUT_IF_ABSENT,
+
+ /**
+ * Update entry if the current version matches specified version.
+ */
+ PUT_IF_VERSION_MATCH,
+
+ /**
+ * Update entry if the current value matches specified value.
+ */
+ PUT_IF_VALUE_MATCH,
+
+ /**
+ * Remove entry without any checks.
+ */
+ REMOVE,
+
+ /**
+ * Remove entry if the current version matches specified version.
+ */
+ REMOVE_IF_VERSION_MATCH,
+
+ /**
+ * Remove entry if the current value matches specified value.
+ */
+ REMOVE_IF_VALUE_MATCH,
+ }
+
+ private Type type;
+ private String mapName;
+ private String key;
+ private byte[] value;
+ private byte[] currentValue;
+ private long currentVersion = -1;
+
+ /**
+ * Returns the type of update operation.
+ * @return type of update.
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the name of map being updated.
+ * @return map name.
+ */
+ public String mapName() {
+ return mapName;
+ }
+
+ /**
+ * Returns the item key being updated.
+ * @return item key
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns the new value.
+ * @return item's target value.
+ */
+ public byte[] value() {
+ return value;
+ }
+
+ /**
+ * Returns the expected current value in the database value for the key.
+ * @return current value in database.
+ */
+ public byte[] currentValue() {
+ return currentValue;
+ }
+
+ /**
+ * Returns the expected current version in the database for the key.
+ * @return expected version.
+ */
+ public long currentVersion() {
+ return currentVersion;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("type", type)
+ .add("mapName", mapName)
+ .add("key", key)
+ .add("value", value)
+ .add("currentValue", currentValue)
+ .add("currentVersion", currentVersion)
+ .toString();
+ }
+
+ /**
+ * Creates a new builder instance.
+ *
+ * @return builder.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * DatabaseUpdate builder.
+ *
+ */
+ public static final class Builder {
+
+ private DatabaseUpdate update = new DatabaseUpdate();
+
+ public DatabaseUpdate build() {
+ validateInputs();
+ return update;
+ }
+
+ public Builder withType(Type type) {
+ update.type = checkNotNull(type, "type cannot be null");
+ return this;
+ }
+
+ public Builder withMapName(String mapName) {
+ update.mapName = checkNotNull(mapName, "mapName cannot be null");
+ return this;
+ }
+
+ public Builder withKey(String key) {
+ update.key = checkNotNull(key, "key cannot be null");
+ return this;
+ }
+
+ public Builder withCurrentValue(byte[] value) {
+ update.currentValue = checkNotNull(value, "currentValue cannot be null");
+ return this;
+ }
+
+ public Builder withValue(byte[] value) {
+ update.value = checkNotNull(value, "value cannot be null");
+ return this;
+ }
+
+ public Builder withCurrentVersion(long version) {
+ checkArgument(version >= 0, "version cannot be negative");
+ update.currentVersion = version;
+ return this;
+ }
+
+ private void validateInputs() {
+ checkNotNull(update.type, "type must be specified");
+ checkNotNull(update.mapName, "map name must be specified");
+ checkNotNull(update.key, "key must be specified");
+ switch (update.type) {
+ case PUT:
+ case PUT_IF_ABSENT:
+ checkNotNull(update.value, "value must be specified.");
+ break;
+ case PUT_IF_VERSION_MATCH:
+ checkNotNull(update.value, "value must be specified.");
+ checkState(update.currentVersion >= 0, "current version must be specified");
+ break;
+ case PUT_IF_VALUE_MATCH:
+ checkNotNull(update.value, "value must be specified.");
+ checkNotNull(update.currentValue, "currentValue must be specified.");
+ break;
+ case REMOVE:
+ break;
+ case REMOVE_IF_VERSION_MATCH:
+ checkState(update.currentVersion >= 0, "current version must be specified");
+ break;
+ case REMOVE_IF_VALUE_MATCH:
+ checkNotNull(update.currentValue, "currentValue must be specified.");
+ break;
+ default:
+ throw new IllegalStateException("Unknown operation type");
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueue.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueue.java
new file mode 100644
index 00000000..cc0b00d3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueue.java
@@ -0,0 +1,62 @@
+/*
+ * 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.store.service;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A distributed collection designed for holding elements prior to processing.
+ * A queue provides insertion, extraction and inspection operations. The extraction operation
+ * is designed to be non-blocking.
+ *
+ * @param <E> queue entry type
+ */
+public interface DistributedQueue<E> {
+
+ /**
+ * Returns total number of entries in the queue.
+ * @return queue size
+ */
+ long size();
+
+ /**
+ * Returns true if queue has elements in it.
+ * @return true is queue has elements, false otherwise
+ */
+ default boolean isEmpty() {
+ return size() == 0;
+ }
+
+ /**
+ * Inserts an entry into the queue.
+ * @param entry entry to insert
+ */
+ void push(E entry);
+
+ /**
+ * If the queue is non-empty, an entry will be removed from the queue and the returned future
+ * will be immediately completed with it. If queue is empty when this call is made, the returned
+ * future will be eventually completed when an entry is added to the queue.
+ * @return queue entry
+ */
+ CompletableFuture<E> pop();
+
+ /**
+ * Returns an entry from the queue without removing it. If the queue is empty returns null.
+ * @return queue entry or null if queue is empty
+ */
+ E peek();
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueueBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueueBuilder.java
new file mode 100644
index 00000000..646dc28c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedQueueBuilder.java
@@ -0,0 +1,79 @@
+/*
+ * 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.store.service;
+
+/**
+ * Builder for distributed queue.
+ *
+ * @param <E> type queue elements.
+ */
+public interface DistributedQueueBuilder<E> {
+
+ /**
+ * Sets the name of the queue.
+ * <p>
+ * Each queue is identified by a unique name.
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param name name of the queue
+ * @return this DistributedQueueBuilder for method chaining
+ */
+ DistributedQueueBuilder<E> withName(String name);
+
+ /**
+ * Sets a serializer that can be used to serialize
+ * the elements pushed into the queue. The serializer
+ * builder should be pre-populated with any classes that will be
+ * put into the queue.
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param serializer serializer
+ * @return this DistributedQueueBuilder for method chaining
+ */
+ DistributedQueueBuilder<E> withSerializer(Serializer serializer);
+
+ /**
+ *
+ *
+ * @return this DistributedQueueBuilder for method chaining
+ */
+ DistributedQueueBuilder<E> withMeteringDisabled();
+
+
+ /**
+ * Disables persistence of queues entries.
+ * <p>
+ * When persistence is disabled, a full cluster restart will wipe out all
+ * queue entries.
+ * </p>
+ * @return this DistributedQueueBuilder for method chaining
+ */
+ DistributedQueueBuilder<E> withPersistenceDisabled();
+
+ /**
+ * Builds a queue based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new distributed queue
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ DistributedQueue<E> build();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSet.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSet.java
new file mode 100644
index 00000000..460206ec
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSet.java
@@ -0,0 +1,41 @@
+/*
+ * 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.store.service;
+
+import java.util.Set;
+
+/**
+ * A distributed collection designed for holding unique elements.
+ *
+ * @param <E> set entry type
+ */
+public interface DistributedSet<E> extends Set<E> {
+
+ /**
+ * Registers the specified listener to be notified whenever
+ * the set is updated.
+ *
+ * @param listener listener to notify about set update events
+ */
+ void addListener(SetEventListener<E> listener);
+
+ /**
+ * Unregisters the specified listener.
+ *
+ * @param listener listener to unregister.
+ */
+ void removeListener(SetEventListener<E> listener);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSetBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSetBuilder.java
new file mode 100644
index 00000000..f5a44c93
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/DistributedSetBuilder.java
@@ -0,0 +1,132 @@
+/*
+ * 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.store.service;
+
+import org.onosproject.core.ApplicationId;
+
+/**
+ * Builder for distributed set.
+ *
+ * @param <E> type set elements.
+ */
+public interface DistributedSetBuilder<E> {
+
+ /**
+ * Sets the name of the set.
+ * <p>
+ * Each set is identified by a unique name.
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param name name of the set
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withName(String name);
+
+ /**
+ * Sets the owner applicationId for the set.
+ * <p>
+ * Note: If {@code purgeOnUninstall} option is enabled, applicationId
+ * must be specified.
+ * </p>
+ *
+ * @param id applicationId owning the set
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withApplicationId(ApplicationId id);
+
+ /**
+ * Sets a serializer that can be used to serialize
+ * the elements add to the set. The serializer
+ * builder should be pre-populated with any classes that will be
+ * put into the set.
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param serializer serializer
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withSerializer(Serializer serializer);
+
+ /**
+ * Disables set updates.
+ * <p>
+ * Attempt to update the built set will throw {@code UnsupportedOperationException}.
+ *
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withUpdatesDisabled();
+
+ /**
+ * Provides weak consistency for set reads.
+ * <p>
+ * While this can lead to improved read performance, it can also make the behavior
+ * heard to reason. Only turn this on if you know what you are doing. By default
+ * reads are strongly consistent.
+ *
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withRelaxedReadConsistency();
+
+ /**
+ * Disables distribution of set entries across multiple database partitions.
+ * <p>
+ * When partitioning is disabled, the returned set will have a single partition
+ * that spans the entire cluster. Furthermore, the changes made to the set are
+ * ephemeral and do not survive a full cluster restart.
+ * </p>
+ * <p>
+ * Disabling partitions is more appropriate when the returned set is used for
+ * simple coordination activities and not for long term data persistence.
+ * </p>
+ * <p>
+ * Note: By default partitions are enabled and entries in the set are durable.
+ * </p>
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withPartitionsDisabled();
+
+ /**
+ * Instantiate Metrics service to gather usage and performance metrics.
+ * By default usage information is enabled
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withMeteringDisabled();
+
+ /**
+ * Purges set contents when the application owning the set is uninstalled.
+ * <p>
+ * When this option is enabled, the caller must provide a applicationId via
+ * the {@code withAppliationId} builder method.
+ * <p>
+ * By default set contents will NOT be purged when owning application is uninstalled.
+ *
+ * @return this DistributedSetBuilder
+ */
+ DistributedSetBuilder<E> withPurgeOnUninstall();
+
+ /**
+ * Builds an set based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new set
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ DistributedSet<E> build();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMap.java
new file mode 100644
index 00000000..06395b8e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMap.java
@@ -0,0 +1,207 @@
+/*
+ * 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.store.service;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * A distributed, eventually consistent map.
+ * <p>
+ * This map does not offer read after writes consistency. Operations are
+ * serialized via the timestamps issued by the clock service. If two updates
+ * are in conflict, the update with the more recent timestamp will endure.
+ * </p><p>
+ * The interface is mostly similar to {@link java.util.Map} with some minor
+ * semantic changes and the addition of a listener framework (because the map
+ * can be mutated by clients on other instances, not only through the local Java
+ * API).
+ * </p><p>
+ * Clients are expected to register an
+ * {@link EventuallyConsistentMapListener} if they
+ * are interested in receiving notifications of update to the map.
+ * </p><p>
+ * Null values are not allowed in this map.
+ * </p>
+ */
+public interface EventuallyConsistentMap<K, V> {
+
+ /**
+ * Returns the number of key-value mappings in this map.
+ *
+ * @return number of key-value mappings
+ */
+ int size();
+
+ /**
+ * Returns true if this map is empty.
+ *
+ * @return true if this map is empty, otherwise false
+ */
+ boolean isEmpty();
+
+ /**
+ * Returns true if the map contains a mapping for the specified key.
+ *
+ * @param key the key to check if this map contains
+ * @return true if this map has a mapping for the key, otherwise false
+ */
+ boolean containsKey(K key);
+
+ /**
+ * Returns true if the map contains a mapping from any key to the specified
+ * value.
+ *
+ * @param value the value to check if this map has a mapping for
+ * @return true if this map has a mapping to this value, otherwise false
+ */
+ boolean containsValue(V value);
+
+ /**
+ * Returns the value mapped to the specified key.
+ *
+ * @param key the key to look up in this map
+ * @return the value mapped to the key, or null if no mapping is found
+ */
+ V get(K key);
+
+ /**
+ * Associates the specified value to the specified key in this map.
+ * <p>
+ * Note: this differs from the specification of {@link java.util.Map}
+ * because it does not return the previous value associated with the key.
+ * Clients are expected to register an
+ * {@link EventuallyConsistentMapListener} if
+ * they are interested in receiving notification of updates to the map.
+ * </p><p>
+ * Null values are not allowed in the map.
+ * </p>
+ *
+ * @param key the key to add a mapping for in this map
+ * @param value the value to associate with the key in this map
+ */
+ void put(K key, V value);
+
+ /**
+ * Removes the mapping associated with the specified key from the map.
+ * <p>
+ * Clients are expected to register an {@link EventuallyConsistentMapListener} if
+ * they are interested in receiving notification of updates to the map.
+ * </p>
+ *
+ * @param key the key to remove the mapping for
+ * @return previous value associated with key, or null if there was no mapping for key.
+ */
+ V remove(K key);
+
+ /**
+ * Removes the given key-value mapping from the map, if it exists.
+ * <p>
+ * This actually means remove any values up to and including the timestamp
+ * given by the map's timestampProvider.
+ * Any mappings that produce an earlier timestamp than this given key-value
+ * pair will be removed, and any mappings that produce a later timestamp
+ * will supersede this remove.
+ * </p><p>
+ * Note: this differs from the specification of {@link java.util.Map}
+ * because it does not return a boolean indication whether a value was removed.
+ * Clients are expected to register an
+ * {@link EventuallyConsistentMapListener} if
+ * they are interested in receiving notification of updates to the map.
+ * </p>
+ *
+ * @param key the key to remove the mapping for
+ * @param value the value mapped to the key
+ */
+ void remove(K key, V value);
+
+ /**
+ * Attempts to compute a mapping for the specified key and its current mapped
+ * value (or null if there is no current mapping).
+ * <p>
+ * If the function returns null, the mapping is removed (or remains absent if initially absent).
+ * @param key map key
+ * @param recomputeFunction function to recompute a new value
+ * @return new value
+ */
+ V compute(K key, BiFunction<K, V, V> recomputeFunction);
+
+ /**
+ * Adds mappings for all key-value pairs in the specified map to this map.
+ * <p>
+ * This will be more efficient in communication than calling individual put
+ * operations.
+ * </p>
+ *
+ * @param m a map of values to add to this map
+ */
+ void putAll(Map<? extends K, ? extends V> m);
+
+ /**
+ * Removes all mappings from this map.
+ */
+ void clear();
+
+ /**
+ * Returns a set of the keys in this map. Changes to the set are not
+ * reflected back to the map.
+ *
+ * @return set of keys in the map
+ */
+ Set<K> keySet();
+
+ /**
+ * Returns a collections of values in this map. Changes to the collection
+ * are not reflected back to the map.
+ *
+ * @return collection of values in the map
+ */
+ Collection<V> values();
+
+ /**
+ * Returns a set of mappings contained in this map. Changes to the set are
+ * not reflected back to the map.
+ *
+ * @return set of key-value mappings in this map
+ */
+ Set<Map.Entry<K, V>> entrySet();
+
+ /**
+ * Adds the specified listener to the map which will be notified whenever
+ * the mappings in the map are changed.
+ *
+ * @param listener listener to register for events
+ */
+ void addListener(EventuallyConsistentMapListener<K, V> listener);
+
+ /**
+ * Removes the specified listener from the map such that it will no longer
+ * receive change notifications.
+ *
+ * @param listener listener to deregister for events
+ */
+ void removeListener(EventuallyConsistentMapListener<K, V> listener);
+
+ /**
+ * Shuts down the map and breaks communication between different instances.
+ * This allows the map objects to be cleaned up and garbage collected.
+ * Calls to any methods on the map subsequent to calling destroy() will
+ * throw a {@link java.lang.RuntimeException}.
+ */
+ void destroy();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapBuilder.java
new file mode 100644
index 00000000..9471321c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapBuilder.java
@@ -0,0 +1,187 @@
+/*
+ * 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.store.service;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.Timestamp;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+
+/**
+ * Builder for eventually consistent maps.
+ *
+ * @param <K> type for map keys
+ * @param <V> type for map values
+ */
+public interface EventuallyConsistentMapBuilder<K, V> {
+
+ /**
+ * Sets the name of the map.
+ * <p>
+ * Each map is identified by a string map name. EventuallyConsistentMapImpl
+ * objects in different JVMs that use the same map name will form a
+ * distributed map across JVMs (provided the cluster service is aware of
+ * both nodes).
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param name name of the map
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withName(String name);
+
+ /**
+ * Sets a serializer builder that can be used to create a serializer that
+ * can serialize both the keys and values put into the map. The serializer
+ * builder should be pre-populated with any classes that will be put into
+ * the map.
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param serializerBuilder serializer builder
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withSerializer(
+ KryoNamespace.Builder serializerBuilder);
+
+ /**
+ * Sets the function to use for generating timestamps for map updates.
+ * <p>
+ * The client must provide an {@code BiFunction<K, V, Timestamp>}
+ * which can generate timestamps for a given key. The function is free
+ * to generate timestamps however it wishes, however these timestamps will
+ * be used to serialize updates to the map so they must be strict enough
+ * to ensure updates are properly ordered for the use case (i.e. in some
+ * cases wallclock time will suffice, whereas in other cases logical time
+ * will be necessary).
+ * </p>
+ * <p>
+ * Note: This is a mandatory parameter.
+ * </p>
+ *
+ * @param timestampProvider provides a new timestamp
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withTimestampProvider(
+ BiFunction<K, V, Timestamp> timestampProvider);
+
+ /**
+ * Sets the executor to use for processing events coming in from peers.
+ *
+ * @param executor event executor
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withEventExecutor(
+ ExecutorService executor);
+
+ /**
+ * Sets the executor to use for sending events to peers.
+ *
+ * @param executor event executor
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withCommunicationExecutor(
+ ExecutorService executor);
+
+ /**
+ * Sets the executor to use for background anti-entropy tasks.
+ *
+ * @param executor event executor
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withBackgroundExecutor(
+ ScheduledExecutorService executor);
+
+ /**
+ * Sets a function that can determine which peers to replicate updates to.
+ * <p>
+ * The default function replicates to all nodes.
+ * </p>
+ *
+ * @param peerUpdateFunction function that takes a K, V input and returns
+ * a collection of NodeIds to replicate the event
+ * to
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withPeerUpdateFunction(
+ BiFunction<K, V, Collection<NodeId>> peerUpdateFunction);
+
+ /**
+ * Prevents this map from writing tombstones of items that have been
+ * removed. This may result in zombie items reappearing after they have
+ * been removed.
+ * <p>
+ * The default behavior is tombstones are enabled.
+ * </p>
+ *
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withTombstonesDisabled();
+
+ /**
+ * Configures how often to run the anti-entropy background task.
+ * <p>
+ * The default anti-entropy period is 5 seconds.
+ * </p>
+ *
+ * @param period anti-entropy period
+ * @param unit time unit for the period
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withAntiEntropyPeriod(
+ long period, TimeUnit unit);
+
+ /**
+ * Configure anti-entropy to converge faster at the cost of doing more work
+ * for each anti-entropy cycle. Suited to maps with low update rate where
+ * convergence time is more important than throughput.
+ * <p>
+ * The default behavior is to do less anti-entropy work at the cost of
+ * slower convergence.
+ * </p>
+ *
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withFasterConvergence();
+
+ /**
+ * Configure the map to persist data to disk.
+ * <p>
+ * The default behavior is no persistence
+ * </p>
+ *
+ * @return this EventuallyConsistentMapBuilder
+ */
+ EventuallyConsistentMapBuilder<K, V> withPersistence();
+
+ /**
+ * Builds an eventually consistent map based on the configuration options
+ * supplied to this builder.
+ *
+ * @return new eventually consistent map
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ EventuallyConsistentMap<K, V> build();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapEvent.java
new file mode 100644
index 00000000..5814534e
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapEvent.java
@@ -0,0 +1,124 @@
+/*
+ * 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.store.service;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Representation of a EventuallyConsistentMap update notification.
+ */
+public final class EventuallyConsistentMapEvent<K, V> {
+
+ public enum Type {
+ /**
+ * Entry added to map or existing entry updated.
+ */
+ PUT,
+
+ /**
+ * Entry removed from map.
+ */
+ REMOVE
+ }
+
+ private final String name;
+ private final Type type;
+ private final K key;
+ private final V value;
+
+ /**
+ * Creates a new event object.
+ *
+ * @param name map name
+ * @param type the type of the event
+ * @param key the key the event concerns
+ * @param value the value mapped to the key
+ */
+ public EventuallyConsistentMapEvent(String name, Type type, K key, V value) {
+ this.name = name;
+ this.type = type;
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Returns the map name.
+ *
+ * @return name of map
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return the type of the event
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the key this event concerns.
+ *
+ * @return the key
+ */
+ public K key() {
+ return key;
+ }
+
+ /**
+ * Returns the value associated with this event. If type is REMOVE,
+ * this is the value that was removed. If type is PUT, this is
+ * the new value.
+ *
+ * @return the value
+ */
+ public V value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof EventuallyConsistentMapEvent)) {
+ return false;
+ }
+
+ EventuallyConsistentMapEvent that = (EventuallyConsistentMapEvent) o;
+ return Objects.equals(this.type, that.type) &&
+ Objects.equals(this.key, that.key) &&
+ Objects.equals(this.value, that.value) &&
+ Objects.equals(this.name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, key, value);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .add("type", type)
+ .add("key", key)
+ .add("value", value)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapListener.java
new file mode 100644
index 00000000..b2399a48
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/EventuallyConsistentMapListener.java
@@ -0,0 +1,29 @@
+/*
+ * 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.store.service;
+
+/**
+ * Listener to be notified about updates to a EventuallyConsistentMap.
+ */
+public interface EventuallyConsistentMapListener<K, V> {
+
+ /**
+ * Reacts to the specified event.
+ *
+ * @param event the event
+ */
+ void event(EventuallyConsistentMapEvent<K, V> event);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/LogicalClockService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/LogicalClockService.java
new file mode 100644
index 00000000..7592b126
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/LogicalClockService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.store.service;
+
+import org.onosproject.store.Timestamp;
+
+/**
+ * Service that issues logical timestamps.
+ * <p>
+ * Logical timestamps are useful for establishing a total ordering of
+ * arbitrary cluster wide events without relying on a fully synchronized
+ * system clock (wall clock)
+ */
+public interface LogicalClockService {
+
+ /**
+ * Generates a new logical timestamp.
+ *
+ * @return timestamp
+ */
+ Timestamp getTimestamp();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEvent.java
new file mode 100644
index 00000000..6e671351
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEvent.java
@@ -0,0 +1,135 @@
+/*
+ * 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.store.service;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Representation of a ConsistentMap update notification.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public class MapEvent<K, V> {
+
+ /**
+ * MapEvent type.
+ */
+ public enum Type {
+ /**
+ * Entry inserted into the map.
+ */
+ INSERT,
+
+ /**
+ * Existing map entry updated.
+ */
+ UPDATE,
+
+ /**
+ * Entry removed from map.
+ */
+ REMOVE
+ }
+
+ private final String name;
+ private final Type type;
+ private final K key;
+ private final Versioned<V> value;
+
+ /**
+ * Creates a new event object.
+ *
+ * @param name map name
+ * @param type type of event
+ * @param key key the event concerns
+ * @param value value key is mapped to
+ */
+ public MapEvent(String name, Type type, K key, Versioned<V> value) {
+ this.name = name;
+ this.type = type;
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Returns the map name.
+ *
+ * @return name of map
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return the type of event
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the key this event concerns.
+ *
+ * @return the key
+ */
+ public K key() {
+ return key;
+ }
+
+ /**
+ * Returns the value associated with this event. If type is REMOVE,
+ * this is the value that was removed. If type is INSERT/UPDATE, this is
+ * the new value.
+ *
+ * @return the value
+ */
+ public Versioned<V> value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof MapEvent)) {
+ return false;
+ }
+
+ MapEvent<K, V> that = (MapEvent) o;
+ return Objects.equals(this.name, that.name) &&
+ Objects.equals(this.type, that.type) &&
+ Objects.equals(this.key, that.key) &&
+ Objects.equals(this.value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, key, value);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .add("type", type)
+ .add("key", key)
+ .add("value", value)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEventListener.java
new file mode 100644
index 00000000..359f4653
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapEventListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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.store.service;
+
+/**
+ * Listener to be notified about updates to a ConsistentMap.
+ */
+public interface MapEventListener<K, V> {
+ /**
+ * Reacts to the specified event.
+ *
+ * @param event the event
+ */
+ void event(MapEvent<K, V> event);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapInfo.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapInfo.java
new file mode 100644
index 00000000..5db17049
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MapInfo.java
@@ -0,0 +1,47 @@
+/*
+ * 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.store.service;
+
+/**
+ * Metadata information for a consistent map.
+ */
+public class MapInfo {
+ private final String name;
+ private final int size;
+
+ public MapInfo(String name, int size) {
+ this.name = name;
+ this.size = size;
+ }
+
+ /**
+ * Returns the name of the map.
+ *
+ * @return map name
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the number of entries in the map.
+ *
+ * @return map size
+ */
+ public int size() {
+ return size;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MultiValuedTimestamp.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MultiValuedTimestamp.java
new file mode 100644
index 00000000..e068b8e9
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/MultiValuedTimestamp.java
@@ -0,0 +1,111 @@
+/*
+ * 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.store.service;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import org.onosproject.store.Timestamp;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A logical timestamp that derives its value from two input values. The first
+ * value always takes precedence over the second value when comparing timestamps.
+ */
+public class MultiValuedTimestamp<T extends Comparable<T>, U extends Comparable<U>>
+ implements Timestamp {
+
+ private final T value1;
+ private final U value2;
+
+ /**
+ * Creates a new timestamp based on two values. The first value has higher
+ * precedence than the second when comparing timestamps.
+ *
+ * @param value1 first value
+ * @param value2 second value
+ */
+ public MultiValuedTimestamp(T value1, U value2) {
+ this.value1 = checkNotNull(value1);
+ this.value2 = checkNotNull(value2);
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof MultiValuedTimestamp,
+ "Must be MultiValuedTimestamp", o);
+ MultiValuedTimestamp that = (MultiValuedTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.value1, that.value1)
+ .compare(this.value2, that.value2)
+ .result();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value1, value2);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof MultiValuedTimestamp)) {
+ return false;
+ }
+ MultiValuedTimestamp that = (MultiValuedTimestamp) obj;
+ return Objects.equals(this.value1, that.value1) &&
+ Objects.equals(this.value2, that.value2);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("value1", value1)
+ .add("value2", value2)
+ .toString();
+ }
+
+ /**
+ * Returns the first value.
+ *
+ * @return first value
+ */
+ public T value1() {
+ return value1;
+ }
+
+ /**
+ * Returns the second value.
+ *
+ * @return second value
+ */
+ public U value2() {
+ return value2;
+ }
+
+ // Default constructor for serialization
+ @SuppressWarnings("unused")
+ private MultiValuedTimestamp() {
+ this.value1 = null;
+ this.value2 = null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/PartitionInfo.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/PartitionInfo.java
new file mode 100644
index 00000000..a0f06481
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/PartitionInfo.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.store.service;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Contains information about a database partition.
+ */
+public class PartitionInfo {
+ private final String name;
+ private final long term;
+ private final List<String> members;
+ private final String leader;
+
+ /**
+ * Class constructor.
+ *
+ * @param name partition name
+ * @param term term number
+ * @param members partition members
+ * @param leader leader name
+ */
+ public PartitionInfo(String name, long term, List<String> members, String leader) {
+ this.name = name;
+ this.term = term;
+ this.members = ImmutableList.copyOf(members);
+ this.leader = leader;
+ }
+
+ /**
+ * Returns the name of the partition.
+ *
+ * @return partition name
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the term number.
+ *
+ * @return term number
+ */
+ public long term() {
+ return term;
+ }
+
+ /**
+ * Returns the list of partition members.
+ *
+ * @return partition members
+ */
+ public List<String> members() {
+ return members;
+ }
+
+ /**
+ * Returns the partition leader.
+ *
+ * @return partition leader
+ */
+ public String leader() {
+ return leader;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Serializer.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Serializer.java
new file mode 100644
index 00000000..6245175f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Serializer.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.store.service;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.onlab.util.KryoNamespace;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Interface for serialization of store artifacts.
+ */
+public interface Serializer {
+ /**
+ * Serialize the specified object.
+ * @param object object to serialize.
+ * @return serialized bytes.
+ * @param <T> encoded type
+ */
+ <T> byte[] encode(T object);
+
+ /**
+ * Deserialize the specified bytes.
+ * @param bytes byte array to deserialize.
+ * @return deserialized object.
+ * @param <T> decoded type
+ */
+ <T> T decode(byte[] bytes);
+
+ /**
+ * Creates a new Serializer instance from a KryoNamespace.
+ *
+ * @param kryo kryo namespace
+ * @return Serializer instance
+ */
+ static Serializer using(KryoNamespace kryo) {
+ return using(Arrays.asList(kryo));
+ }
+
+ static Serializer using(List<KryoNamespace> namespaces, Class<?>... classes) {
+ KryoNamespace.Builder builder = new KryoNamespace.Builder();
+ namespaces.forEach(builder::register);
+ Lists.newArrayList(classes).forEach(builder::register);
+ builder.register(MapEvent.class, MapEvent.Type.class, Versioned.class);
+ KryoNamespace namespace = builder.build();
+ return new Serializer() {
+ @Override
+ public <T> byte[] encode(T object) {
+ return namespace.serialize(object);
+ }
+
+ @Override
+ public <T> T decode(byte[] bytes) {
+ return namespace.deserialize(bytes);
+ }
+ };
+ }
+
+ static Serializer forTypes(Class<?>... classes) {
+ return using(KryoNamespace.newBuilder()
+ .register(classes)
+ .register(MapEvent.class, MapEvent.Type.class)
+ .build());
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEvent.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEvent.java
new file mode 100644
index 00000000..a869e722
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEvent.java
@@ -0,0 +1,113 @@
+/*
+ * 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.store.service;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Representation of a DistributedSet update notification.
+ *
+ * @param <E> set element type
+ */
+public final class SetEvent<E> {
+
+ /**
+ * SetEvent type.
+ */
+ public enum Type {
+ /**
+ * Entry added to the set.
+ */
+ ADD,
+
+ /**
+ * Entry removed from the set.
+ */
+ REMOVE
+ }
+
+ private final String name;
+ private final Type type;
+ private final E entry;
+
+ /**
+ * Creates a new event object.
+ *
+ * @param name set name
+ * @param type type of the event
+ * @param entry entry the event concerns
+ */
+ public SetEvent(String name, Type type, E entry) {
+ this.name = name;
+ this.type = type;
+ this.entry = entry;
+ }
+
+ /**
+ * Returns the set name.
+ *
+ * @return name of set
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return type of the event
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the entry this event concerns.
+ *
+ * @return the entry
+ */
+ public E entry() {
+ return entry;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof SetEvent)) {
+ return false;
+ }
+
+ SetEvent that = (SetEvent) o;
+ return Objects.equals(this.name, that.name) &&
+ Objects.equals(this.type, that.type) &&
+ Objects.equals(this.entry, that.entry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, entry);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .add("type", type)
+ .add("entry", entry)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEventListener.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEventListener.java
new file mode 100644
index 00000000..a64994ef
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/SetEventListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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.store.service;
+
+/**
+ * Listener to be notified about updates to a DistributedSet.
+ */
+public interface SetEventListener<E> {
+ /**
+ * Reacts to the specified event.
+ *
+ * @param event the event
+ */
+ void event(SetEvent<E> event);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageAdminService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageAdminService.java
new file mode 100644
index 00000000..22591044
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageAdminService.java
@@ -0,0 +1,75 @@
+/*
+ * 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.store.service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Service for administering storage instances.
+ */
+public interface StorageAdminService {
+
+ /**
+ * Returns information about all partitions in the system.
+ *
+ * @return list of partition information
+ */
+ List<PartitionInfo> getPartitionInfo();
+
+ /**
+ * Returns information about all the consistent maps in the system.
+ *
+ * @return list of map information
+ */
+ List<MapInfo> getMapInfo();
+
+ /**
+ * Returns information about all the atomic counters in the system.
+ * If 2 counters belonging to 2 different databases have the same name,
+ * then only one counter from one database is returned.
+ *
+ * @return mapping from counter name to that counter's next value
+ */
+ Map<String, Long> getCounters();
+
+ /**
+ * Returns information about all the atomic partitioned database counters in the system.
+ *
+ * @return mapping from counter name to that counter's next value
+ */
+ Map<String, Long> getPartitionedDatabaseCounters();
+
+ /**
+ * Returns information about all the atomic in-memory database counters in the system.
+ *
+ * @return mapping from counter name to that counter's next value
+ */
+ Map<String, Long> getInMemoryDatabaseCounters();
+
+ /**
+ * Returns all the transactions in the system.
+ *
+ * @return collection of transactions
+ */
+ Collection<Transaction> getTransactions();
+
+ /**
+ * Redrives stuck transactions while removing those that are done.
+ */
+ void redriveTransactions();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageException.java
new file mode 100644
index 00000000..a66fc3ed
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.store.service;
+
+/**
+ * Top level exception for Store failures.
+ */
+@SuppressWarnings("serial")
+public class StorageException extends RuntimeException {
+ public StorageException() {
+ }
+
+ public StorageException(Throwable t) {
+ super(t);
+ }
+
+ /**
+ * Store operation timeout.
+ */
+ public static class Timeout extends StorageException {
+ }
+
+ /**
+ * Store update conflicts with an in flight transaction.
+ */
+ public static class ConcurrentModification extends StorageException {
+ }
+
+ /**
+ * Store operation interrupted.
+ */
+ public static class Interrupted extends StorageException {
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageService.java
new file mode 100644
index 00000000..f6b411d3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/StorageService.java
@@ -0,0 +1,83 @@
+/*
+ * 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.store.service;
+
+/**
+ * Storage service.
+ * <p>
+ * This service provides builders for various distributed primitives.
+ * <p>
+ * It is expected that services and applications will leverage the primitives indirectly provided by
+ * this service for their distributed state management and coordination.
+ */
+public interface StorageService {
+
+ /**
+ * Creates a new EventuallyConsistentMapBuilder.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ * @return builder for an eventually consistent map
+ */
+ <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder();
+
+ /**
+ * Creates a new ConsistentMapBuilder.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ * @return builder for a consistent map
+ */
+ <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder();
+
+ /**
+ * Creates a new DistributedSetBuilder.
+ *
+ * @param <E> set element type
+ * @return builder for an distributed set
+ */
+ <E> DistributedSetBuilder<E> setBuilder();
+
+ /**
+ * Creates a new DistributedQueueBuilder.
+ *
+ * @param <E> queue entry type
+ * @return builder for an distributed queue
+ */
+ <E> DistributedQueueBuilder<E> queueBuilder();
+
+ /**
+ * Creates a new AtomicCounterBuilder.
+ *
+ * @return atomic counter builder
+ */
+ AtomicCounterBuilder atomicCounterBuilder();
+
+ /**
+ * Creates a new AtomicValueBuilder.
+ *
+ * @param <V> atomic value type
+ * @return atomic value builder
+ */
+ <V> AtomicValueBuilder<V> atomicValueBuilder();
+
+ /**
+ * Creates a new transaction context builder.
+ *
+ * @return a builder for a transaction context.
+ */
+ TransactionContextBuilder transactionContextBuilder();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Transaction.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Transaction.java
new file mode 100644
index 00000000..330d8468
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Transaction.java
@@ -0,0 +1,102 @@
+/*
+ * 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.store.service;
+
+import java.util.List;
+
+/**
+ * An immutable transaction object.
+ */
+public interface Transaction {
+
+ enum State {
+ /**
+ * Indicates a new transaction that is about to be prepared. All transactions
+ * start their life in this state.
+ */
+ PREPARING,
+
+ /**
+ * Indicates a transaction that is successfully prepared i.e. all participants voted to commit
+ */
+ PREPARED,
+
+ /**
+ * Indicates a transaction that is about to be committed.
+ */
+ COMMITTING,
+
+ /**
+ * Indicates a transaction that has successfully committed.
+ */
+ COMMITTED,
+
+ /**
+ * Indicates a transaction that is about to be rolled back.
+ */
+ ROLLINGBACK,
+
+ /**
+ * Indicates a transaction that has been rolled back and all locks are released.
+ */
+ ROLLEDBACK
+ }
+
+ /**
+ * Returns the transaction Id.
+ *
+ * @return transaction id
+ */
+ long id();
+
+ /**
+ * Returns the list of updates that are part of this transaction.
+ *
+ * @return list of database updates
+ */
+ List<DatabaseUpdate> updates();
+
+ /**
+ * Returns the current state of this transaction.
+ *
+ * @return transaction state
+ */
+ State state();
+
+ /**
+ * Returns true if this transaction has completed execution.
+ *
+ * @return true is yes, false otherwise
+ */
+ default boolean isDone() {
+ return state() == State.COMMITTED || state() == State.ROLLEDBACK;
+ }
+
+ /**
+ * Returns a new transaction that is created by transitioning this one to the specified state.
+ *
+ * @param newState destination state
+ * @return a new transaction instance similar to the current one but its state set to specified state
+ */
+ Transaction transition(State newState);
+
+ /**
+ * Returns the system time when the transaction was last updated.
+ *
+ * @return last update time
+ */
+ long lastUpdated();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContext.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContext.java
new file mode 100644
index 00000000..94942e20
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContext.java
@@ -0,0 +1,78 @@
+/*
+ * 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.store.service;
+
+/**
+ * Provides a context for transactional operations.
+ * <p>
+ * A transaction context is a vehicle for grouping operations into a unit with the
+ * properties of atomicity, isolation, and durability. Transactions also provide the
+ * ability to maintain an application's invariants or integrity constraints,
+ * supporting the property of consistency. Together these properties are known as ACID.
+ * <p>
+ * A transaction context provides a boundary within which transactions
+ * are run. It also is a place where all modifications made within a transaction
+ * are cached until the point when the transaction commits or aborts. It thus ensures
+ * isolation of work happening with in the transaction boundary. Within a transaction
+ * context isolation level is REPEATABLE_READS i.e. only data that is committed can be read.
+ * The only uncommitted data that can be read is the data modified by the current transaction.
+ */
+public interface TransactionContext {
+
+ /**
+ * Returns the unique transactionId.
+ *
+ * @return transaction id
+ */
+ long transactionId();
+
+ /**
+ * Returns if this transaction context is open.
+ *
+ * @return true if open, false otherwise
+ */
+ boolean isOpen();
+
+ /**
+ * Starts a new transaction.
+ */
+ void begin();
+
+ /**
+ * Commits a transaction that was previously started thereby making its changes permanent
+ * and externally visible.
+ *
+ * @throws TransactionException if transaction fails to commit
+ */
+ void commit();
+
+ /**
+ * Aborts any changes made in this transaction context and discarding all locally cached updates.
+ */
+ void abort();
+
+ /**
+ * Returns a transactional map data structure with the specified name.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ * @param mapName name of the transactional map
+ * @param serializer serializer to use for encoding/decoding keys and values of the map
+ * @return Transactional Map
+ */
+ <K, V> TransactionalMap<K, V> getTransactionalMap(String mapName, Serializer serializer);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContextBuilder.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContextBuilder.java
new file mode 100644
index 00000000..e9f3a020
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionContextBuilder.java
@@ -0,0 +1,47 @@
+/*
+ * 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.store.service;
+
+/**
+ * Interface definition for a transaction context builder.
+ */
+public interface TransactionContextBuilder {
+
+ /**
+ * Disables distribution of map entries across multiple database partitions.
+ * <p>
+ * When partitioning is disabled, the returned map will have a single
+ * partition that spans the entire cluster. Furthermore, the changes made to
+ * the map are ephemeral and do not survive a full cluster restart.
+ * </p>
+ * <p>
+ * Note: By default, partitions are enabled. This feature is intended to
+ * simplify debugging.
+ * </p>
+ *
+ * @return this TransactionalContextBuilder
+ */
+ TransactionContextBuilder withPartitionsDisabled();
+
+ /**
+ * Builds a TransactionContext based on configuration options supplied to this
+ * builder.
+ *
+ * @return a new TransactionalContext
+ * @throws java.lang.RuntimeException if a mandatory parameter is missing
+ */
+ TransactionContext build();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionException.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionException.java
new file mode 100644
index 00000000..a3723ee7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionException.java
@@ -0,0 +1,54 @@
+/*
+ * 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.store.service;
+
+/**
+ * Top level exception for Transaction failures.
+ */
+@SuppressWarnings("serial")
+public class TransactionException extends StorageException {
+ public TransactionException() {
+ }
+
+ public TransactionException(Throwable t) {
+ super(t);
+ }
+
+ /**
+ * Transaction timeout.
+ */
+ public static class Timeout extends TransactionException {
+ }
+
+ /**
+ * Transaction interrupted.
+ */
+ public static class Interrupted extends TransactionException {
+ }
+
+ /**
+ * Transaction failure due to optimistic concurrency violation.
+ */
+ public static class OptimisticConcurrencyFailure extends TransactionException {
+ }
+
+ /**
+ * Transaction failure due to a conflicting transaction in progress.
+ */
+ public static class ConcurrentModification extends TransactionException {
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionalMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionalMap.java
new file mode 100644
index 00000000..657d9331
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/TransactionalMap.java
@@ -0,0 +1,93 @@
+/*
+ * 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.store.service;
+
+/**
+ * Transactional Map data structure.
+ * <p>
+ * A TransactionalMap is created by invoking {@link TransactionContext#getTransactionalMap getTransactionalMap}
+ * method. All operations performed on this map within a transaction boundary are invisible externally
+ * until the point when the transaction commits. A commit usually succeeds in the absence of conflicts.
+ *
+ * @param <K> type of key.
+ * @param <V> type of value.
+ */
+public interface TransactionalMap<K, V> {
+
+ /**
+ * Returns the value to which the specified key is mapped, or null if this
+ * map contains no mapping for the key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or null if
+ * this map contains no mapping for the key
+ */
+ V get(K key);
+
+ /**
+ * Associates the specified value with the specified key in this map (optional operation).
+ * If the map previously contained a mapping for the key, the old value is replaced by the
+ * specified value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with key, or null if there was
+ * no mapping for key.
+ */
+ V put(K key, V value);
+
+ /**
+ * Removes the mapping for a key from this map if it is present (optional operation).
+ *
+ * @param key key whose value is to be removed from the map
+ * @return the value to which this map previously associated the key,
+ * or null if the map contained no mapping for the key.
+ */
+ V remove(K key);
+
+ /**
+ * If the specified key is not already associated with a value
+ * associates it with the given value and returns null, else returns the current value.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key or null
+ * if key does not already mapped to a value.
+ */
+ V putIfAbsent(K key, V value);
+
+ /**
+ * Removes the entry for the specified key only if it is currently
+ * mapped to the specified value.
+ *
+ * @param key key with which the specified value is associated
+ * @param value value expected to be associated with the specified key
+ * @return true if the value was removed
+ */
+ boolean remove(K key, V value);
+
+ /**
+ * Replaces the entry for the specified key only if currently mapped
+ * to the specified value.
+ *
+ * @param key key with which the specified value is associated
+ * @param oldValue value expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return true if the value was replaced
+ */
+ boolean replace(K key, V oldValue, V newValue);
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Versioned.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Versioned.java
new file mode 100644
index 00000000..89bd3029
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/Versioned.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.store.service;
+
+import java.util.function.Function;
+
+import org.joda.time.DateTime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Versioned value.
+ *
+ * @param <V> value type.
+ */
+public class Versioned<V> {
+
+ private final V value;
+ private final long version;
+ private final long creationTime;
+
+ /**
+ * Constructs a new versioned value.
+ * @param value value
+ * @param version version
+ * @param creationTime milliseconds of the creation event
+ * from the Java epoch of 1970-01-01T00:00:00Z
+ */
+ public Versioned(V value, long version, long creationTime) {
+ this.value = value;
+ this.version = version;
+ this.creationTime = creationTime;
+ }
+
+ /**
+ * Constructs a new versioned value.
+ * @param value value
+ * @param version version
+ */
+ public Versioned(V value, long version) {
+ this(value, version, System.currentTimeMillis());
+ }
+
+ /**
+ * Returns the value.
+ *
+ * @return value.
+ */
+ public V value() {
+ return value;
+ }
+
+ /**
+ * Returns the version.
+ *
+ * @return version
+ */
+ public long version() {
+ return version;
+ }
+
+ /**
+ * Returns the system time when this version was created.
+ * <p>
+ * Care should be taken when relying on creationTime to
+ * implement any behavior in a distributed setting. Due
+ * to the possibility of clock skew it is likely that
+ * even creationTimes of causally related versions can be
+ * out or order.
+ * @return creation time
+ */
+ public long creationTime() {
+ return creationTime;
+ }
+
+ /**
+ * Maps this instance into another after transforming its
+ * value while retaining the same version and creationTime.
+ * @param transformer function for mapping the value
+ * @param <U> value type of the returned instance
+ * @return mapped instance
+ */
+ public <U> Versioned<U> map(Function<V, U> transformer) {
+ return new Versioned<>(transformer.apply(value), version, creationTime);
+ }
+
+ /**
+ * Returns the value of the specified Versioned object if non-null or else returns
+ * a default value.
+ * @param versioned versioned object
+ * @param defaultValue default value to return if versioned object is null
+ * @param <U> type of the versioned value
+ * @return versioned value or default value if versioned object is null
+ */
+ public static <U> U valueOrElse(Versioned<U> versioned, U defaultValue) {
+ return versioned == null ? defaultValue : versioned.value();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(value, version, creationTime);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Versioned)) {
+ return false;
+ }
+ Versioned<V> that = (Versioned) other;
+ return Objects.equal(this.value, that.value) &&
+ Objects.equal(this.version, that.version) &&
+ Objects.equal(this.creationTime, that.creationTime);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("value", value)
+ .add("version", version)
+ .add("creationTime", new DateTime(creationTime))
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/WallClockTimestamp.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/WallClockTimestamp.java
new file mode 100644
index 00000000..0cc7b453
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/WallClockTimestamp.java
@@ -0,0 +1,85 @@
+/*
+ * 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.store.service;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Objects;
+
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+
+/**
+ * A Timestamp that derives its value from the prevailing
+ * wallclock time on the controller where it is generated.
+ */
+public class WallClockTimestamp implements Timestamp {
+
+ private final long unixTimestamp;
+
+ public WallClockTimestamp() {
+ unixTimestamp = System.currentTimeMillis();
+ }
+
+ public WallClockTimestamp(long timestamp) {
+ unixTimestamp = timestamp;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof WallClockTimestamp,
+ "Must be WallClockTimestamp", o);
+ WallClockTimestamp that = (WallClockTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.unixTimestamp, that.unixTimestamp)
+ .result();
+ }
+ @Override
+ public int hashCode() {
+ return Objects.hash(unixTimestamp);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof WallClockTimestamp)) {
+ return false;
+ }
+ WallClockTimestamp that = (WallClockTimestamp) obj;
+ return Objects.equals(this.unixTimestamp, that.unixTimestamp);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("unixTimestamp", unixTimestamp)
+ .toString();
+ }
+
+ /**
+ * Returns the unixTimestamp.
+ *
+ * @return unix timestamp
+ */
+ public long unixTimestamp() {
+ return unixTimestamp;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/package-info.java
new file mode 100644
index 00000000..3d86e351
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/store/service/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.
+ */
+
+/**
+ * Distributed core state management services.
+ */
+package org.onosproject.store.service;
+
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
new file mode 100644
index 00000000..2ebb5545
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.ui;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Provides convenience methods for dealing with JSON nodes, arrays etc.
+ */
+public final class JsonUtils {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // non-instantiable
+ private JsonUtils() { }
+
+ /**
+ * Wraps a message payload into an event structure for the given event
+ * type and sequence ID. Generally, the sequence ID should be a copy of
+ * the ID from the client request event.
+ *
+ * @param type event type
+ * @param sid sequence ID
+ * @param payload event payload
+ * @return the object node representation
+ */
+ public static ObjectNode envelope(String type, long sid, ObjectNode payload) {
+ ObjectNode event = MAPPER.createObjectNode();
+ event.put("event", type);
+ if (sid > 0) {
+ event.put("sid", sid);
+ }
+ event.set("payload", payload);
+ return event;
+ }
+
+ /**
+ * Composes a message structure for the given message type and payload.
+ *
+ * @param type message type
+ * @param payload message payload
+ * @return the object node representation
+ */
+ public static ObjectNode envelope(String type, ObjectNode payload) {
+ ObjectNode event = MAPPER.createObjectNode();
+ event.put("event", type);
+ event.set("payload", payload);
+ return event;
+ }
+
+ /**
+ * Returns the event type from the specified event.
+ * If the node does not have an "event" property, "unknown" is returned.
+ *
+ * @param event message event
+ * @return extracted event type
+ */
+ public static String eventType(ObjectNode event) {
+ return string(event, "event", "unknown");
+ }
+
+ /**
+ * Returns the sequence identifier from the specified event, or 0 (zero)
+ * if the "sid" property does not exist.
+ *
+ * @param event message event
+ * @return extracted sequence identifier
+ */
+ public static long sid(ObjectNode event) {
+ return number(event, "sid");
+ }
+
+ /**
+ * Returns the payload from the specified event.
+ *
+ * @param event message event
+ * @return extracted payload object
+ */
+ public static ObjectNode payload(ObjectNode event) {
+ return (ObjectNode) event.path("payload");
+ }
+
+ /**
+ * Returns the specified node property as a number.
+ *
+ * @param node message event
+ * @param name property name
+ * @return property as number
+ */
+ public static long number(ObjectNode node, String name) {
+ return node.path(name).asLong();
+ }
+
+ /**
+ * Returns the specified node property as a string.
+ *
+ * @param node message event
+ * @param name property name
+ * @return property as a string
+ */
+ public static String string(ObjectNode node, String name) {
+ return node.path(name).asText();
+ }
+
+ /**
+ * Returns the specified node property as a string, with a default fallback.
+ *
+ * @param node object node
+ * @param name property name
+ * @param defaultValue fallback value if property is absent
+ * @return property as a string
+ */
+ public static String string(ObjectNode node, String name, String defaultValue) {
+ return node.path(name).asText(defaultValue);
+ }
+
+ /**
+ * Returns the specified node property as an object node.
+ *
+ * @param node object node
+ * @param name property name
+ * @return property as a node
+ */
+ public static ObjectNode node(ObjectNode node, String name) {
+ return (ObjectNode) node.path(name);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
new file mode 100644
index 00000000..1678923d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.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.ui;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Abstraction of an entity that handles a specific request from the
+ * user interface client.
+ *
+ * @see UiMessageHandler
+ */
+public abstract class RequestHandler {
+
+ protected static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final String eventType;
+ private UiMessageHandler parent;
+
+
+ public RequestHandler(String eventType) {
+ this.eventType = eventType;
+ }
+
+ // package private
+ void setParent(UiMessageHandler parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * Returns the event type that this handler handles.
+ *
+ * @return event type
+ */
+ public String eventType() {
+ return eventType;
+ }
+
+ /**
+ * Processes the incoming message payload from the client.
+ *
+ * @param sid message sequence identifier
+ * @param payload request message payload
+ */
+ // TODO: remove sid from signature
+ public abstract void process(long sid, ObjectNode payload);
+
+
+
+ // ===================================================================
+ // === Convenience methods...
+
+ /**
+ * Returns implementation of the specified service class.
+ *
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return implementation class
+ * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+ */
+ protected <T> T get(Class<T> serviceClass) {
+ return parent.directory().get(serviceClass);
+ }
+
+ /**
+ * Sends a message back to the client.
+ *
+ * @param eventType message event type
+ * @param sid message sequence identifier
+ * @param payload message payload
+ */
+ // TODO: remove sid from signature
+ protected void sendMessage(String eventType, long sid, ObjectNode payload) {
+ parent.connection().sendMessage(eventType, sid, payload);
+ }
+
+ /**
+ * Sends a message back to the client.
+ * Here, the message is preformatted; the assumption is it has its
+ * eventType, sid and payload attributes already filled in.
+ *
+ * @param message the message to send
+ */
+ protected void sendMessage(ObjectNode message) {
+ parent.connection().sendMessage(message);
+ }
+
+ /**
+ * Allows one request handler to pass the event on to another for
+ * further processing.
+ * Note that the message handlers must be defined in the same parent.
+ *
+ * @param eventType event type
+ * @param sid sequence identifier
+ * @param payload message payload
+ */
+ // TODO: remove sid from signature
+ protected void chain(String eventType, long sid, ObjectNode payload) {
+ parent.exec(eventType, sid, payload);
+ }
+
+ // ===================================================================
+
+
+ /**
+ * Returns the specified node property as a string.
+ *
+ * @param node message event
+ * @param key property name
+ * @return property as a string
+ */
+ protected String string(ObjectNode node, String key) {
+ return JsonUtils.string(node, key);
+ }
+
+ /**
+ * Returns the specified node property as a string, with a default fallback.
+ *
+ * @param node object node
+ * @param key property name
+ * @param defValue fallback value if property is absent
+ * @return property as a string
+ */
+ protected String string(ObjectNode node, String key, String defValue) {
+ return JsonUtils.string(node, key, defValue);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java
new file mode 100644
index 00000000..ead7b0dc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java
@@ -0,0 +1,42 @@
+/*
+ * 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.ui;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Abstraction of a user interface session connection.
+ */
+public interface UiConnection {
+
+ /**
+ * Sends the specified JSON message to the user interface client.
+ *
+ * @param message message to send
+ */
+ void sendMessage(ObjectNode message);
+
+ /**
+ * Composes a message into JSON and sends it to the user interface client.
+ *
+ * @param type message type
+ * @param sid message sequence number
+ * @param payload message payload
+ */
+ // TODO: remove sid parameter
+ void sendMessage(String type, long sid, ObjectNode payload);
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java
new file mode 100644
index 00000000..1f5fbd48
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java
@@ -0,0 +1,200 @@
+/*
+ * 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.ui;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * User interface extension.
+ */
+public final class UiExtension {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String VIEW_PREFIX = "app/view/";
+ private static final String EMPTY = "";
+ private static final String SLASH = "/";
+ private static final String CSS_HTML = "css.html";
+ private static final String JS_HTML = "js.html";
+
+ private final ClassLoader classLoader;
+ private final String resourcePath;
+ private final List<UiView> views;
+ private final UiMessageHandlerFactory messageHandlerFactory;
+ private final UiTopoOverlayFactory topoOverlayFactory;
+
+
+ // private constructor - only the builder calls this
+ private UiExtension(ClassLoader cl, String path, List<UiView> views,
+ UiMessageHandlerFactory mhFactory,
+ UiTopoOverlayFactory toFactory) {
+ this.classLoader = cl;
+ this.resourcePath = path;
+ this.views = views;
+ this.messageHandlerFactory = mhFactory;
+ this.topoOverlayFactory = toFactory;
+ }
+
+
+ /**
+ * Returns input stream containing CSS inclusion statements.
+ *
+ * @return CSS inclusion statements
+ */
+ public InputStream css() {
+ return getStream(resourcePath + CSS_HTML);
+ }
+
+ /**
+ * Returns input stream containing JavaScript inclusion statements.
+ *
+ * @return JavaScript inclusion statements
+ */
+ public InputStream js() {
+ return getStream(resourcePath + JS_HTML);
+ }
+
+ /**
+ * Returns list of user interface views contributed by this extension.
+ *
+ * @return contributed view descriptors
+ */
+ public List<UiView> views() {
+ return views;
+ }
+
+ /**
+ * Returns input stream containing specified view-specific resource.
+ *
+ * @param viewId view identifier
+ * @param path resource path, relative to the view directory
+ * @return resource input stream
+ */
+ public InputStream resource(String viewId, String path) {
+ return getStream(VIEW_PREFIX + viewId + SLASH + path);
+ }
+
+ /**
+ * Returns message handler factory, if one was defined.
+ *
+ * @return message handler factory
+ */
+ public UiMessageHandlerFactory messageHandlerFactory() {
+ return messageHandlerFactory;
+ }
+
+ /**
+ * Returns the topology overlay factory, if one was defined.
+ *
+ * @return topology overlay factory
+ */
+ public UiTopoOverlayFactory topoOverlayFactory() {
+ return topoOverlayFactory;
+ }
+
+
+ // Returns the resource input stream from the specified class-loader.
+ private InputStream getStream(String path) {
+ InputStream stream = classLoader.getResourceAsStream(path);
+ if (stream == null) {
+ log.warn("Unable to find resource {}", path);
+ }
+ return stream;
+ }
+
+
+ /**
+ * UI Extension Builder.
+ */
+ public static class Builder {
+ private ClassLoader classLoader;
+
+ private String resourcePath = EMPTY;
+ private List<UiView> views = new ArrayList<>();
+ private UiMessageHandlerFactory messageHandlerFactory = null;
+ private UiTopoOverlayFactory topoOverlayFactory = null;
+
+ /**
+ * Create a builder with the given class loader.
+ * Resource path defaults to "".
+ * Views defaults to an empty list.
+ * Both Message and TopoOverlay factories default to null.
+ *
+ * @param cl the class loader
+ * @param views list of views contributed by this extension
+ */
+ public Builder(ClassLoader cl, List<UiView> views) {
+ checkNotNull(cl, "Must provide a class loader");
+ checkArgument(views.size() > 0, "Must provide at least one view");
+ this.classLoader = cl;
+ this.views = views;
+ }
+
+ /**
+ * Set the resource path. That is, path to where the CSS and JS
+ * files are located. This value should
+ *
+ * @param path resource path
+ * @return self, for chaining
+ */
+ public Builder resourcePath(String path) {
+ this.resourcePath = path == null ? EMPTY : path + SLASH;
+ return this;
+ }
+
+ /**
+ * Sets the message handler factory for this extension.
+ *
+ * @param mhFactory message handler factory
+ * @return self, for chaining
+ */
+ public Builder messageHandlerFactory(UiMessageHandlerFactory mhFactory) {
+ this.messageHandlerFactory = mhFactory;
+ return this;
+ }
+
+ /**
+ * Sets the topology overlay factory for this extension.
+ *
+ * @param toFactory topology overlay factory
+ * @return self, for chaining
+ */
+ public Builder topoOverlayFactory(UiTopoOverlayFactory toFactory) {
+ this.topoOverlayFactory = toFactory;
+ return this;
+ }
+
+ /**
+ * Builds the UI extension.
+ *
+ * @return UI extension instance
+ */
+ public UiExtension build() {
+ return new UiExtension(classLoader, resourcePath, views,
+ messageHandlerFactory, topoOverlayFactory);
+ }
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
new file mode 100644
index 00000000..330fbb7a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.ui;
+
+import java.util.List;
+
+/**
+ * Service for registering user interface extensions.
+ */
+public interface UiExtensionService {
+
+ /**
+ * Registers the specified user interface extension.
+ *
+ * @param extension GUI extension to register
+ */
+ void register(UiExtension extension);
+
+ /**
+ * Unregisters the specified user interface extension.
+ *
+ * @param extension GUI extension to unregister
+ */
+ void unregister(UiExtension extension);
+
+ /**
+ * Returns the list of user interface extensions.
+ *
+ * @return list of extensions
+ */
+ List<UiExtension> getExtensions();
+
+ /**
+ * Returns the user interface extension that contributed the specified view.
+ *
+ * @param viewId view identifier
+ * @return user interface extension
+ */
+ UiExtension getViewExtension(String viewId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
new file mode 100644
index 00000000..9b9a406c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
@@ -0,0 +1,207 @@
+/*
+ * 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.ui;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.osgi.ServiceDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of an entity capable of processing JSON messages from the user
+ * interface client.
+ * <p>
+ * The message structure is:
+ * </p>
+ * <pre>
+ * {
+ * "type": "<em>event-type</em>",
+ * "payload": {
+ * <em>arbitrary JSON object structure</em>
+ * }
+ * }
+ * </pre>
+ * On {@link #init initialization} the handler will create and cache
+ * {@link RequestHandler} instances, each of which are bound to a particular
+ * <em>event-type</em>. On {@link #process arrival} of a new message,
+ * the <em>event-type</em> is determined, and the message dispatched to the
+ * corresponding <em>RequestHandler</em>'s
+ * {@link RequestHandler#process process} method.
+ */
+public abstract class UiMessageHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private final Map<String, RequestHandler> handlerMap = new HashMap<>();
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private UiConnection connection;
+ private ServiceDirectory directory;
+
+
+ /**
+ * Subclasses must create and return the collection of request handlers
+ * for the message types they handle.
+ * <p>
+ * Note that request handlers should be stateless. When we are
+ * {@link #destroy destroyed}, we will simply drop our references to them
+ * and allow them to be garbage collected.
+ *
+ * @return the message handler instances
+ */
+ protected abstract Collection<RequestHandler> createRequestHandlers();
+
+ /**
+ * Returns the set of message types which this handler is capable of
+ * processing.
+ *
+ * @return set of message types
+ */
+ public Set<String> messageTypes() {
+ return Collections.unmodifiableSet(handlerMap.keySet());
+ }
+
+ /**
+ * Processes a JSON message from the user interface client.
+ *
+ * @param message JSON message
+ */
+ public void process(ObjectNode message) {
+ String type = JsonUtils.eventType(message);
+ ObjectNode payload = JsonUtils.payload(message);
+ // TODO: remove sid
+ exec(type, 0, payload);
+ }
+
+ /**
+ * Finds the appropriate handler and executes the process method.
+ *
+ * @param eventType event type
+ * @param sid sequence identifier
+ * @param payload message payload
+ */
+ // TODO: remove sid from signature
+ void exec(String eventType, long sid, ObjectNode payload) {
+ RequestHandler requestHandler = handlerMap.get(eventType);
+ if (requestHandler != null) {
+ requestHandler.process(sid, payload);
+ } else {
+ log.warn("no request handler for event type {}", eventType);
+ }
+ }
+
+ /**
+ * Initializes the handler with the user interface connection and
+ * service directory context.
+ *
+ * @param connection user interface connection
+ * @param directory service directory
+ */
+ public void init(UiConnection connection, ServiceDirectory directory) {
+ this.connection = connection;
+ this.directory = directory;
+
+ Collection<RequestHandler> handlers = createRequestHandlers();
+ checkNotNull(handlers, "Handlers cannot be null");
+ checkArgument(!handlers.isEmpty(), "Handlers cannot be empty");
+
+ for (RequestHandler h : handlers) {
+ h.setParent(this);
+ handlerMap.put(h.eventType(), h);
+ }
+ }
+
+ /**
+ * Destroys the message handler context.
+ */
+ public void destroy() {
+ this.connection = null;
+ this.directory = null;
+ handlerMap.clear();
+ }
+
+ /**
+ * Returns the user interface connection with which this handler was primed.
+ *
+ * @return user interface connection
+ */
+ public UiConnection connection() {
+ return connection;
+ }
+
+ /**
+ * Returns the user interface connection with which this handler was primed.
+ *
+ * @return user interface connection
+ */
+ public ServiceDirectory directory() {
+ return directory;
+ }
+
+ /**
+ * Returns implementation of the specified service class.
+ *
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return implementation class
+ * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+ */
+ protected <T> T get(Class<T> serviceClass) {
+ return directory.get(serviceClass);
+ }
+
+ /**
+ * Returns a freshly minted object node.
+ *
+ * @return new object node
+ */
+ protected ObjectNode objectNode() {
+ return mapper.createObjectNode();
+ }
+
+ /**
+ * Returns a freshly minted array node.
+ *
+ * @return new array node
+ */
+ protected ArrayNode arrayNode() {
+ return mapper.createArrayNode();
+ }
+
+ /**
+ * Sends the specified data to the client.
+ * It is expected that the data is in the prescribed JSON format for
+ * events to the client.
+ *
+ * @param data data to be sent
+ */
+ protected synchronized void sendMessage(ObjectNode data) {
+ UiConnection connection = connection();
+ if (connection != null) {
+ connection.sendMessage(data);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
new file mode 100644
index 00000000..522daa8f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ui;
+
+import java.util.Collection;
+
+/**
+ * Abstraction of an entity capable of producing a set of message handlers
+ * specific to the given user interface connection.
+ */
+public interface UiMessageHandlerFactory {
+
+ /**
+ * Produces a collection of new message handlers.
+ *
+ * @return collection of new handlers
+ */
+ Collection<UiMessageHandler> newHandlers();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
new file mode 100644
index 00000000..2889422a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
@@ -0,0 +1,122 @@
+/*
+ * 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.ui;
+
+import org.onosproject.ui.topo.PropertyPanel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents user interface topology view overlay.
+ */
+public class UiTopoOverlay {
+
+ /**
+ * Logger for this overlay.
+ */
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final String id;
+
+ private boolean isActive = false;
+
+ /**
+ * Creates a new user interface topology view overlay descriptor.
+ *
+ * @param id overlay identifier
+ */
+ public UiTopoOverlay(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the identifier for this overlay.
+ *
+ * @return the identifier
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Callback invoked to initialize this overlay, soon after creation.
+ * This default implementation does nothing.
+ */
+ public void init() {
+ }
+
+ /**
+ * Callback invoked when this overlay is activated.
+ */
+ public void activate() {
+ isActive = true;
+ }
+
+ /**
+ * Callback invoked when this overlay is deactivated.
+ */
+ public void deactivate() {
+ isActive = false;
+ }
+
+ /**
+ * Returns true if this overlay is currently active.
+ *
+ * @return true if overlay active
+ */
+ public boolean isActive() {
+ return isActive;
+ }
+
+ /**
+ * Callback invoked to destroy this instance by cleaning up any
+ * internal state ready for garbage collection.
+ * This default implementation holds no state and does nothing.
+ */
+ public void destroy() {
+ }
+
+ /**
+ * Callback to modify the contents of the summary panel.
+ * This default implementation does nothing.
+ *
+ * @param pp property panel model of summary data
+ */
+ public void modifySummary(PropertyPanel pp) {
+ }
+
+ /**
+ * Callback to modify the contents of the details panel for
+ * a selected device.
+ * This default implementation does nothing.
+ *
+ * @param pp property panel model of summary data
+ */
+ public void modifyDeviceDetails(PropertyPanel pp) {
+ }
+
+ /**
+ * Callback to modify the contents of the details panel for
+ * a selected host.
+ * This default implementation does nothing.
+ *
+ * @param pp property panel model of summary data
+ */
+ public void modifyHostDetails(PropertyPanel pp) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java
new file mode 100644
index 00000000..bd2f2fe6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ui;
+
+import java.util.Collection;
+
+/**
+ * Abstraction of an entity capable of producing one or more topology
+ * overlay handlers specific to a given user interface connection.
+ */
+public interface UiTopoOverlayFactory {
+
+ /**
+ * Produces a collection of new overlay handlers.
+ *
+ * @return collection of new overlay handlers
+ */
+ Collection<UiTopoOverlay> newOverlays();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java
new file mode 100644
index 00000000..2b8b7fa2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java
@@ -0,0 +1,165 @@
+/*
+ * 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.ui;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Represents user interface view addition.
+ */
+public class UiView {
+
+ /**
+ * Designates navigation menu category.
+ */
+ public enum Category {
+ /**
+ * Represents platform related views.
+ */
+ PLATFORM("Platform"),
+
+ /**
+ * Represents network-control related views.
+ */
+ NETWORK("Network"),
+
+ /**
+ * Represents miscellaneous views.
+ */
+ OTHER("Other"),
+
+ /**
+ * Represents views that do not show in the navigation menu.
+ * This category should not be specified directly; rather, use
+ * the {@link UiViewHidden} constructor instead of {@link UiView}.
+ */
+ HIDDEN("(hidden)");
+
+ private final String label;
+
+ Category(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Returns display label for the category.
+ *
+ * @return display label
+ */
+ public String label() {
+ return label;
+ }
+ }
+
+ private final Category category;
+ private final String id;
+ private final String label;
+ private final String iconId;
+
+ /**
+ * Creates a new user interface view descriptor. The navigation item
+ * will appear in the navigation menu under the specified category.
+ *
+ * @param category view category
+ * @param id view identifier
+ * @param label view label
+ */
+ public UiView(Category category, String id, String label) {
+ this(category, id, label, null);
+ }
+
+ /**
+ * Creates a new user interface view descriptor. The navigation item
+ * will appear in the navigation menu under the specified category,
+ * with the specified icon adornment.
+ *
+ * @param category view category
+ * @param id view identifier
+ * @param label view label
+ * @param iconId icon id
+ */
+ public UiView(Category category, String id, String label, String iconId) {
+ this.category = category;
+ this.id = id;
+ this.label = label;
+ this.iconId = iconId;
+ }
+
+ /**
+ * Returns the navigation category.
+ *
+ * @return navigation category
+ */
+ public Category category() {
+ return category;
+ }
+
+ /**
+ * Returns the view identifier.
+ *
+ * @return view id
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the view label.
+ *
+ * @return view label
+ */
+ public String label() {
+ return label;
+ }
+
+ /**
+ * Returns the icon ID.
+ *
+ * @return icon ID
+ */
+ public String iconId() {
+ return iconId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final UiView other = (UiView) obj;
+ return Objects.equals(this.id, other.id);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("category", category)
+ .add("id", id)
+ .add("label", label)
+ .add("iconId", iconId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
new file mode 100644
index 00000000..b7fea8fe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ui;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents user interface view addition, except that this one should not
+ * have an entry in the navigation panel.
+ */
+public class UiViewHidden extends UiView {
+
+ /**
+ * Creates a new user interface hidden view descriptor.
+ *
+ * @param id view identifier
+ */
+ public UiViewHidden(String id) {
+ super(Category.HIDDEN, id, null);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java
new file mode 100644
index 00000000..dd832a59
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/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.
+ */
+
+/**
+ * Mechanism for managing dynamically registered user interface extensions.
+ */
+package org.onosproject.ui;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java
new file mode 100644
index 00000000..84d11344
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.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.ui.table;
+
+/**
+ * Defines a comparator for cell values.
+ */
+public interface CellComparator {
+
+ /**
+ * Compares its two arguments for order. Returns a negative integer,
+ * zero, or a positive integer as the first argument is less than, equal
+ * to, or greater than the second.<p>
+ *
+ * Note that nulls are permitted, and should be sorted to the beginning
+ * of an ascending sort; i.e. null is considered to be "smaller" than
+ * non-null values.
+ *
+ * @see java.util.Comparator#compare(Object, Object)
+ *
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return a negative integer, zero, or a positive integer as the
+ * first argument is less than, equal to, or greater than the
+ * second.
+ * @throws ClassCastException if the arguments' types prevent them from
+ * being compared by this comparator.
+ */
+ int compare(Object o1, Object o2);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java
new file mode 100644
index 00000000..854ac27f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ui.table;
+
+/**
+ * Defines a formatter for cell values.
+ */
+public interface CellFormatter {
+
+ /**
+ * Formats the specified value into a string appropriate for displaying
+ * in a table cell. Note that null values are acceptable, and will result
+ * in the empty string.
+ *
+ * @param value the value
+ * @return the formatted string
+ */
+ String format(Object value);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
new file mode 100644
index 00000000..d0fccb65
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
@@ -0,0 +1,304 @@
+/*
+ * 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.ui.table;
+
+import com.google.common.collect.Sets;
+import org.onosproject.ui.table.cell.DefaultCellComparator;
+import org.onosproject.ui.table.cell.DefaultCellFormatter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+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;
+
+/**
+ * A simple model of table data.
+ * <p>
+ * Note that this is not a full MVC type model; the expected usage pattern
+ * is to create an empty table, add rows (by consulting the business model),
+ * sort rows (based on client request parameters), and finally produce the
+ * sorted list of rows.
+ * <p>
+ * The table also provides a mechanism for defining how cell values for a
+ * particular column should be formatted into strings, to help facilitate
+ * the encoding of the table data into a JSON structure.
+ * <p>
+ * Note that it is expected that all values for a particular column will
+ * be the same class.
+ */
+public class TableModel {
+
+ private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE;
+ private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE;
+
+ private final String[] columnIds;
+ private final Set<String> idSet;
+ private final Map<String, CellComparator> comparators = new HashMap<>();
+ private final Map<String, CellFormatter> formatters = new HashMap<>();
+ private final List<Row> rows = new ArrayList<>();
+
+
+ /**
+ * Constructs a table (devoid of data) with the given column IDs.
+ *
+ * @param columnIds column identifiers
+ */
+ public TableModel(String... columnIds) {
+ checkNotNull(columnIds, "columnIds cannot be null");
+ checkArgument(columnIds.length > 0, "must be at least one column");
+
+ idSet = Sets.newHashSet(columnIds);
+ if (idSet.size() != columnIds.length) {
+ throw new IllegalArgumentException("duplicate column ID(s) detected");
+ }
+
+ this.columnIds = Arrays.copyOf(columnIds, columnIds.length);
+ }
+
+ private void checkId(String id) {
+ checkNotNull(id, "must provide a column ID");
+ if (!idSet.contains(id)) {
+ throw new IllegalArgumentException("unknown column id: " + id);
+ }
+ }
+
+ /**
+ * Returns the number of rows in this table model.
+ *
+ * @return number of rows
+ */
+ public int rowCount() {
+ return rows.size();
+ }
+
+ /**
+ * Returns the number of columns in this table model.
+ *
+ * @return number of columns
+ */
+ public int columnCount() {
+ return columnIds.length;
+ }
+
+ /**
+ * Returns the array of column IDs for this table model.
+ * <p>
+ * Implementation note: we are knowingly passing you a reference to
+ * our internal array to avoid copying. Don't mess with it. It's your
+ * table you'll break if you do!
+ *
+ * @return the column identifiers
+ */
+ public String[] getColumnIds() {
+ return columnIds;
+ }
+
+ /**
+ * Returns the raw {@link Row} representation of the rows in this table.
+ *
+ * @return raw table rows
+ */
+ public Row[] getRows() {
+ return rows.toArray(new Row[rows.size()]);
+ }
+
+ /**
+ * Sets a cell comparator for the specified column.
+ *
+ * @param columnId column identifier
+ * @param comparator comparator to use
+ */
+ public void setComparator(String columnId, CellComparator comparator) {
+ checkNotNull(comparator, "must provide a comparator");
+ checkId(columnId);
+ comparators.put(columnId, comparator);
+ }
+
+ /**
+ * Returns the cell comparator to use on values in the specified column.
+ *
+ * @param columnId column identifier
+ * @return an appropriate cell comparator
+ */
+ private CellComparator getComparator(String columnId) {
+ checkId(columnId);
+ CellComparator cmp = comparators.get(columnId);
+ return cmp == null ? DEF_CMP : cmp;
+ }
+
+ /**
+ * Sets a cell formatter for the specified column.
+ *
+ * @param columnId column identifier
+ * @param formatter formatter to use
+ */
+ public void setFormatter(String columnId, CellFormatter formatter) {
+ checkNotNull(formatter, "must provide a formatter");
+ checkId(columnId);
+ formatters.put(columnId, formatter);
+ }
+
+ /**
+ * Returns the cell formatter to use on values in the specified column.
+ *
+ * @param columnId column identifier
+ * @return an appropriate cell formatter
+ */
+ public CellFormatter getFormatter(String columnId) {
+ checkId(columnId);
+ CellFormatter fmt = formatters.get(columnId);
+ return fmt == null ? DEF_FMT : fmt;
+ }
+
+ /**
+ * Adds a row to the table model.
+ *
+ * @return the row, for chaining
+ */
+ public Row addRow() {
+ Row r = new Row();
+ rows.add(r);
+ return r;
+ }
+
+ /**
+ * Sorts the table rows based on the specified column, in the
+ * specified direction.
+ *
+ * @param columnId column identifier
+ * @param dir sort direction
+ */
+ public void sort(String columnId, SortDir dir) {
+ Collections.sort(rows, new RowComparator(columnId, dir));
+ }
+
+
+ /** Designates sorting direction. */
+ public enum SortDir {
+ /** Designates an ascending sort. */
+ ASC,
+ /** Designates a descending sort. */
+ DESC
+ }
+
+ /**
+ * Row comparator.
+ */
+ private class RowComparator implements Comparator<Row> {
+ private final String columnId;
+ private final SortDir dir;
+ private final CellComparator cellComparator;
+
+ /**
+ * Constructs a row comparator based on the specified
+ * column identifier and sort direction.
+ *
+ * @param columnId column identifier
+ * @param dir sort direction
+ */
+ public RowComparator(String columnId, SortDir dir) {
+ this.columnId = columnId;
+ this.dir = dir;
+ cellComparator = getComparator(columnId);
+ }
+
+ @Override
+ public int compare(Row a, Row b) {
+ Object cellA = a.get(columnId);
+ Object cellB = b.get(columnId);
+ int result = cellComparator.compare(cellA, cellB);
+ return dir == SortDir.ASC ? result : -result;
+ }
+ }
+
+ /**
+ * Model of a row.
+ */
+ public class Row {
+ private final Map<String, Object> cells = new HashMap<>();
+
+ /**
+ * Sets the cell value for the given column of this row.
+ *
+ * @param columnId column identifier
+ * @param value value to set
+ * @return self, for chaining
+ */
+ public Row cell(String columnId, Object value) {
+ checkId(columnId);
+ cells.put(columnId, value);
+ return this;
+ }
+
+ /**
+ * Returns the value of the cell in the given column for this row.
+ *
+ * @param columnId column identifier
+ * @return cell value
+ */
+ public Object get(String columnId) {
+ return cells.get(columnId);
+ }
+
+ /**
+ * Returns the value of the cell as a string, using the
+ * formatter appropriate for the column.
+ *
+ * @param columnId column identifier
+ * @return formatted cell value
+ */
+ String getAsString(String columnId) {
+ return getFormatter(columnId).format(get(columnId));
+ }
+
+ /**
+ * Returns the row as an array of formatted strings.
+ *
+ * @return the formatted row data
+ */
+ public String[] getAsFormattedStrings() {
+ List<String> formatted = new ArrayList<>(columnCount());
+ for (String c : columnIds) {
+ formatted.add(getAsString(c));
+ }
+ return formatted.toArray(new String[formatted.size()]);
+ }
+ }
+
+ private static final String DESC = "desc";
+
+ /**
+ * Returns the appropriate sort direction for the given string.
+ * <p>
+ * The expected strings are "asc" for {@link SortDir#ASC ascending} and
+ * "desc" for {@link SortDir#DESC descending}. Any other value will
+ * default to ascending.
+ *
+ * @param s sort direction string encoding
+ * @return sort direction
+ */
+ public static SortDir sortDir(String s) {
+ return !DESC.equals(s) ? SortDir.ASC : SortDir.DESC;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
new file mode 100644
index 00000000..b8d48575
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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.ui.table;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.ui.JsonUtils;
+import org.onosproject.ui.RequestHandler;
+
+/**
+ * Message handler specifically for table views.
+ */
+public abstract class TableRequestHandler extends RequestHandler {
+
+ private final String respType;
+ private final String nodeName;
+
+ /**
+ * Constructs a table request handler for a specific table view. When
+ * table requests come in, the handler will generate the appropriate
+ * table rows, sort them according the the request sort parameters, and
+ * send back the response to the client.
+ *
+ * @param reqType type of the request event
+ * @param respType type of the response event
+ * @param nodeName name of JSON node holding row data
+ */
+ public TableRequestHandler(String reqType, String respType, String nodeName) {
+ super(reqType);
+ this.respType = respType;
+ this.nodeName = nodeName;
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ TableModel tm = createTableModel();
+ populateTable(tm, payload);
+
+ String sortCol = JsonUtils.string(payload, "sortCol", defaultColumnId());
+ String sortDir = JsonUtils.string(payload, "sortDir", "asc");
+ tm.sort(sortCol, TableModel.sortDir(sortDir));
+
+ ObjectNode rootNode = MAPPER.createObjectNode();
+ rootNode.set(nodeName, TableUtils.generateArrayNode(tm));
+ sendMessage(respType, 0, rootNode);
+ }
+
+ /**
+ * Creates the table model (devoid of data) using {@link #getColumnIds()}
+ * to initialize it, ready to be populated.
+ * <p>
+ * This default implementation returns a table model with default
+ * formatters and comparators for all columns.
+ *
+ * @return an empty table model
+ */
+ protected TableModel createTableModel() {
+ return new TableModel(getColumnIds());
+ }
+
+ /**
+ * Returns the default column ID to be used when one is not supplied in
+ * the payload as the column on which to sort.
+ * <p>
+ * This default implementation returns "id".
+ *
+ * @return default sort column identifier
+ */
+ protected String defaultColumnId() {
+ return "id";
+ }
+
+ /**
+ * Subclasses should return the array of column IDs with which
+ * to initialize their table model.
+ *
+ * @return the column IDs
+ */
+ protected abstract String[] getColumnIds();
+
+ /**
+ * Subclasses should populate the table model by adding
+ * {@link TableModel.Row rows}.
+ * <pre>
+ * tm.addRow()
+ * .cell(COL_ONE, ...)
+ * .cell(COL_TWO, ...)
+ * ... ;
+ * </pre>
+ * The request payload is provided in case there are request filtering
+ * parameters (other than sort column and sort direction) that are required
+ * to generate the appropriate data.
+ *
+ * @param tm the table model
+ * @param payload request payload
+ */
+ protected abstract void populateTable(TableModel tm, ObjectNode payload);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
new file mode 100644
index 00000000..eb2dff78
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.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.ui.table;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Provides static utility methods for dealing with tables.
+ */
+public final class TableUtils {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // non-instantiable
+ private TableUtils() { }
+
+ /**
+ * Generates a JSON array node from a table model.
+ *
+ * @param tm the table model
+ * @return the array node representation
+ */
+ public static ArrayNode generateArrayNode(TableModel tm) {
+ ArrayNode array = MAPPER.createArrayNode();
+ for (TableModel.Row r : tm.getRows()) {
+ array.add(toJsonNode(r, tm));
+ }
+ return array;
+ }
+
+ private static JsonNode toJsonNode(TableModel.Row row, TableModel tm) {
+ ObjectNode result = MAPPER.createObjectNode();
+ String[] keys = tm.getColumnIds();
+ String[] cells = row.getAsFormattedStrings();
+ int n = keys.length;
+ for (int i = 0; i < n; i++) {
+ result.put(keys[i], cells[i]);
+ }
+ return result;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java
new file mode 100644
index 00000000..6113fc3f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.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.ui.table.cell;
+
+import org.onosproject.ui.table.CellComparator;
+
+/**
+ * Base implementation of a {@link CellComparator}. This class takes care
+ * of dealing with null inputs; subclasses should implement their comparison
+ * knowing that both inputs are guaranteed to be non-null.
+ */
+public abstract class AbstractCellComparator implements CellComparator {
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1 == null && o2 == null) {
+ return 0; // o1 == o2
+ }
+ if (o1 == null) {
+ return -1; // o1 < o2
+ }
+ if (o2 == null) {
+ return 1; // o1 > o2
+ }
+ return nonNullCompare(o1, o2);
+ }
+
+ /**
+ * Compares its two arguments for order. Returns a negative integer,
+ * zero, or a positive integer as the first argument is less than, equal
+ * to, or greater than the second.<p>
+ *
+ * Note that both objects are guaranteed to be non-null.
+ *
+ * @see java.util.Comparator#compare(Object, Object)
+ *
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return a negative integer, zero, or a positive integer as the
+ * first argument is less than, equal to, or greater than the
+ * second.
+ * @throws ClassCastException if the arguments' types prevent them from
+ * being compared by this comparator.
+ */
+ protected abstract int nonNullCompare(Object o1, Object o2);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java
new file mode 100644
index 00000000..33ce2ab5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Base implementation of a {@link CellFormatter}. This class takes care of
+ * dealing with null inputs; subclasses should implement their format method
+ * knowing that the input is guaranteed to be non-null.
+ */
+public abstract class AbstractCellFormatter implements CellFormatter {
+
+ @Override
+ public String format(Object value) {
+ return value == null ? "" : nonNullFormat(value);
+ }
+
+ /**
+ * Formats the specified value into a string appropriate for displaying
+ * in a table cell. Note that value is guaranteed to be non-null.
+ *
+ * @param value the value
+ * @return the formatted string
+ */
+ protected abstract String nonNullFormat(Object value);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java
new file mode 100644
index 00000000..42d684b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats an application identifier as "(app-id) : (app-name)".
+ */
+public final class AppIdFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private AppIdFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ ApplicationId appId = (ApplicationId) value;
+ return appId.id() + " : " + appId.name();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new AppIdFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java
new file mode 100644
index 00000000..fee26154
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats a connect point as "(element-id)/(port)".
+ */
+public final class ConnectPointFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private ConnectPointFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ ConnectPoint cp = (ConnectPoint) value;
+ return cp.elementId() + "/" + cp.port();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new ConnectPointFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java
new file mode 100644
index 00000000..093a20d3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.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.ui.table.cell;
+
+import org.onosproject.ui.table.CellComparator;
+
+/**
+ * A default cell comparator.
+ * <p>
+ * Verifies that the objects being compared are the same class.
+ * Looks to see if the objects being compared implement comparable and, if so,
+ * delegates to that; otherwise, implements a lexicographical compare function
+ * (i.e. string sorting). Uses the objects' toString() method and then
+ * compares the resulting strings. Note that null values are acceptable and
+ * are considered "smaller" than any non-null value.
+ */
+public final class DefaultCellComparator extends AbstractCellComparator {
+
+ // non-instantiable
+ private DefaultCellComparator() { }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected int nonNullCompare(Object o1, Object o2) {
+ if (o1 instanceof Comparable) {
+ // if o2 is not the same class as o1, then compareTo will
+ // throw ClassCastException for us
+ return ((Comparable) o1).compareTo(o2);
+ }
+ return o1.toString().compareTo(o2.toString());
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellComparator INSTANCE = new DefaultCellComparator();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java
new file mode 100644
index 00000000..0efa2ebd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * A default cell formatter. Uses the object's toString() method.
+ */
+public final class DefaultCellFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private DefaultCellFormatter() { }
+
+ @Override
+ public String nonNullFormat(Object value) {
+ return value.toString();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new DefaultCellFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java
new file mode 100644
index 00000000..5b89a0b7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.apache.commons.lang.WordUtils.capitalizeFully;
+
+/**
+ * Formats enum types to be readable strings.
+ */
+public final class EnumFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private EnumFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ return capitalizeFully(value.toString().replace("_", " "));
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new EnumFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java
new file mode 100644
index 00000000..e09982ea
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats integer values as hex strings with a "0x" prefix.
+ */
+public final class HexFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private HexFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ return "0x" + Integer.toHexString((Integer) value);
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new HexFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java
new file mode 100644
index 00000000..fe87c61b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ui.table.cell;
+
+import org.onosproject.net.HostLocation;
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats a host location as "(device-id)/(port)".
+ */
+public final class HostLocationFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private HostLocationFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ HostLocation loc = (HostLocation) value;
+ return loc.deviceId() + "/" + loc.port();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new HostLocationFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java
new file mode 100644
index 00000000..44dc1940
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.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.ui.table.cell;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.util.Locale;
+
+/**
+ * Formats time values using {@link DateTimeFormatter}.
+ */
+public final class TimeFormatter extends AbstractCellFormatter {
+
+ private DateTimeFormatter dtf;
+
+ // NOTE: Unlike other formatters in this package, this one is not
+ // implemented as a Singleton, because instances may be
+ // decorated with alternate locale and/or timezone.
+
+ /**
+ * Constructs a time formatter that uses the default locale and timezone.
+ */
+ public TimeFormatter() {
+ dtf = DateTimeFormat.longTime();
+ }
+
+ /**
+ * Sets the locale to use for formatting the time.
+ *
+ * @param locale locale to use for formatting
+ * @return self, for chaining
+ */
+ public TimeFormatter withLocale(Locale locale) {
+ dtf = dtf.withLocale(locale);
+ return this;
+ }
+
+ /**
+ * Sets the time zone to use for formatting the time.
+ *
+ * @param zone time zone to use
+ * @return self, for chaining
+ */
+ public TimeFormatter withZone(DateTimeZone zone) {
+ dtf = dtf.withZone(zone);
+ return this;
+ }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ return dtf.print((DateTime) value);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java
new file mode 100644
index 00000000..c25bcb06
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/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.
+ */
+
+/**
+ * Set of table cell renderers and comparators for use by GUI apps.
+ */
+package org.onosproject.ui.table.cell; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java
new file mode 100644
index 00000000..ee975d11
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/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.
+ */
+
+/**
+ * Facilities for creating tabular models of data for the GUI.
+ */
+package org.onosproject.ui.table; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
new file mode 100644
index 00000000..ab2ced36
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
@@ -0,0 +1,75 @@
+/*
+ * 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.ui.topo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Partial implementation of the highlighting to apply to topology
+ * view elements.
+ */
+public abstract class AbstractHighlight {
+ private final TopoElementType type;
+ private final String elementId;
+ private boolean keepSubdued = false;
+
+ /**
+ * Constructs the highlight.
+ *
+ * @param type highlight element type
+ * @param elementId element identifier
+ */
+ public AbstractHighlight(TopoElementType type, String elementId) {
+ this.type = checkNotNull(type);
+ this.elementId = checkNotNull(elementId);
+ }
+
+ /**
+ * Sets a flag to tell the renderer to keep this element subdued.
+ */
+ public void keepSubdued() {
+ keepSubdued = true;
+ }
+
+ /**
+ * Returns the element type.
+ *
+ * @return element type
+ */
+ public TopoElementType type() {
+ return type;
+ }
+
+ /**
+ * Returns the element identifier.
+ *
+ * @return element identifier
+ */
+ public String elementId() {
+ return elementId;
+ }
+
+ /**
+ * Returns the subdued flag.
+ *
+ * @return subdued flag
+ */
+ public boolean subdued() {
+ return keepSubdued;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java
new file mode 100644
index 00000000..c37c129b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ui.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+/**
+ * A simple concrete implementation of a {@link BiLink}.
+ * Note that this implementation does not generate any link highlights.
+ */
+public class BaseLink extends BiLink {
+
+ /**
+ * Constructs a base link for the given key and initial link.
+ *
+ * @param key canonical key for this base link
+ * @param link first link
+ */
+ public BaseLink(LinkKey key, Link link) {
+ super(key, link);
+ }
+
+ @Override
+ public LinkHighlight highlight(Enum<?> type) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java
new file mode 100644
index 00000000..720eca49
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java
@@ -0,0 +1,31 @@
+/*
+ * 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.ui.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+/**
+ * Collection of {@link BaseLink}s.
+ */
+public class BaseLinkMap extends BiLinkMap<BaseLink> {
+ @Override
+ public BaseLink create(LinkKey key, Link link) {
+ return new BaseLink(key, link);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java
new file mode 100644
index 00000000..8c95e15d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.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.ui.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of a link and its inverse, as a partial implementation.
+ * <p>
+ * Subclasses will decide how to generate the link highlighting (coloring
+ * and labeling) for the topology view.
+ */
+public abstract class BiLink {
+
+ private final LinkKey key;
+ private final Link one;
+ private Link two;
+
+ /**
+ * Constructs a bi-link for the given key and initial link. It is expected
+ * that the caller will have used {@link TopoUtils#canonicalLinkKey(Link)}
+ * to generate the key.
+ *
+ * @param key canonical key for this bi-link
+ * @param link first link
+ */
+ public BiLink(LinkKey key, Link link) {
+ this.key = checkNotNull(key);
+ this.one = checkNotNull(link);
+ }
+
+ /**
+ * Sets the second link for this bi-link.
+ *
+ * @param link second link
+ */
+ public void setOther(Link link) {
+ this.two = checkNotNull(link);
+ }
+
+ /**
+ * Returns the link identifier in the form expected on the Topology View
+ * in the web client.
+ *
+ * @return link identifier
+ */
+ public String linkId() {
+ return TopoUtils.compactLinkString(one);
+ }
+
+ /**
+ * Returns the key for this bi-link.
+ *
+ * @return the key
+ */
+ public LinkKey key() {
+ return key;
+ }
+
+ /**
+ * Returns the first link in this bi-link.
+ *
+ * @return the first link
+ */
+ public Link one() {
+ return one;
+ }
+
+ /**
+ * Returns the second link in this bi-link.
+ *
+ * @return the second link
+ */
+ public Link two() {
+ return two;
+ }
+
+ /**
+ * Returns the link highlighting to use, based on this bi-link's current
+ * state.
+ *
+ * @param type optional highlighting type parameter
+ * @return link highlighting model
+ */
+ public abstract LinkHighlight highlight(Enum<?> type);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java
new file mode 100644
index 00000000..7bc0e65d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java
@@ -0,0 +1,90 @@
+/*
+ * 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.ui.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a collection of {@link BiLink} concrete classes. These maps
+ * are used to collate a set of unidirectional {@link Link}s into a smaller
+ * set of bi-directional {@link BiLink} derivatives.
+ * <p>
+ * @param <B> the type of bi-link subclass
+ */
+public abstract class BiLinkMap<B extends BiLink> {
+
+ private final Map<LinkKey, B> map = new HashMap<>();
+
+ /**
+ * Creates a new instance of a bi-link. Concrete subclasses should
+ * instantiate and return the appropriate bi-link subclass.
+ *
+ * @param key the link key
+ * @param link the initial link
+ * @return a new instance
+ */
+ protected abstract B create(LinkKey key, Link link);
+
+ /**
+ * Adds the given link to our collection, returning the corresponding
+ * bi-link (creating one if needed necessary).
+ *
+ * @param link the link to add to the collection
+ * @return the corresponding bi-link wrapper
+ */
+ public B add(Link link) {
+ LinkKey key = TopoUtils.canonicalLinkKey(checkNotNull(link));
+ B blink = map.get(key);
+ if (blink == null) {
+ // no bi-link yet exists for this link
+ blink = create(key, link);
+ map.put(key, blink);
+ } else {
+ // we have a bi-link for this link.
+ if (!blink.one().equals(link)) {
+ blink.setOther(link);
+ }
+ }
+ return blink;
+ }
+
+ /**
+ * Returns the bi-link instances in the collection.
+ *
+ * @return the bi-links in this map
+ */
+ public Collection<B> biLinks() {
+ return map.values();
+ }
+
+ /**
+ * Returns the number of bi-links in the collection.
+ *
+ * @return number of bi-links
+ */
+ public int size() {
+ return map.size();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java
new file mode 100644
index 00000000..ca2ecccd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.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.ui.topo;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Designates the identity of a button on the topology view panels.
+ */
+public class ButtonId {
+
+ private final String id;
+
+ /**
+ * Creates a button ID with the given identifier.
+ *
+ * @param id identifier for the button
+ */
+ public ButtonId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the identifier for this button.
+ *
+ * @return identifier
+ */
+ public String id() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id()).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ButtonId that = (ButtonId) o;
+ return id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
new file mode 100644
index 00000000..2985d3d4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ui.topo;
+
+/**
+ * Denotes the highlighting to apply to a device.
+ */
+public class DeviceHighlight extends NodeHighlight {
+
+ public DeviceHighlight(String deviceId) {
+ super(TopoElementType.DEVICE, deviceId);
+ }
+
+ // TODO: implement device highlighting:
+ // - visual highlight
+ // - badging
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
new file mode 100644
index 00000000..be59c26b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
@@ -0,0 +1,190 @@
+/*
+ * 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.ui.topo;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encapsulates highlights to be applied to the topology view, such as
+ * highlighting links, displaying link labels, perhaps even decorating
+ * nodes with badges, etc.
+ */
+public class Highlights {
+
+ private static final String EMPTY = "";
+ private static final String MIN = "min";
+ private static final String MAX = "max";
+
+ /**
+ * A notion of amount.
+ */
+ public enum Amount {
+ ZERO(EMPTY),
+ MINIMALLY(MIN),
+ MAXIMALLY(MAX);
+
+ private final String s;
+ Amount(String str) {
+ s = str;
+ }
+
+ @Override
+ public String toString() {
+ return s;
+ }
+ }
+
+ private final Map<String, DeviceHighlight> devices = new HashMap<>();
+ private final Map<String, HostHighlight> hosts = new HashMap<>();
+ private final Map<String, LinkHighlight> links = new HashMap<>();
+
+ private Amount subdueLevel = Amount.ZERO;
+
+
+ /**
+ * Adds highlighting information for a device.
+ *
+ * @param dh device highlight
+ * @return self, for chaining
+ */
+ public Highlights add(DeviceHighlight dh) {
+ devices.put(dh.elementId(), dh);
+ return this;
+ }
+
+ /**
+ * Adds highlighting information for a host.
+ *
+ * @param hh host highlight
+ * @return self, for chaining
+ */
+ public Highlights add(HostHighlight hh) {
+ hosts.put(hh.elementId(), hh);
+ return this;
+ }
+
+ /**
+ * Adds highlighting information for a link.
+ *
+ * @param lh link highlight
+ * @return self, for chaining
+ */
+ public Highlights add(LinkHighlight lh) {
+ links.put(lh.elementId(), lh);
+ return this;
+ }
+
+ /**
+ * Marks the amount by which all other elements (devices, hosts, links)
+ * not explicitly referenced here will be "subdued" visually.
+ *
+ * @param amount amount to subdue other elements
+ * @return self, for chaining
+ */
+ public Highlights subdueAllElse(Amount amount) {
+ subdueLevel = checkNotNull(amount);
+ return this;
+ }
+
+ /**
+ * Returns the collection of device highlights.
+ *
+ * @return device highlights
+ */
+ public Collection<DeviceHighlight> devices() {
+ return Collections.unmodifiableCollection(devices.values());
+ }
+
+ /**
+ * Returns the collection of host highlights.
+ *
+ * @return host highlights
+ */
+ public Collection<HostHighlight> hosts() {
+ return Collections.unmodifiableCollection(hosts.values());
+ }
+
+ /**
+ * Returns the collection of link highlights.
+ *
+ * @return link highlights
+ */
+ public Collection<LinkHighlight> links() {
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ /**
+ * Returns the amount by which all other elements not explicitly
+ * referenced here should be "subdued".
+ *
+ * @return amount to subdue other elements
+ */
+ public Amount subdueLevel() {
+ return subdueLevel;
+ }
+
+ /**
+ * Returns the node highlight (device or host) for the given element
+ * identifier, or null if no match.
+ *
+ * @param id element identifier
+ * @return corresponding node highlight
+ */
+ public NodeHighlight getNode(String id) {
+ NodeHighlight nh = devices.get(id);
+ return nh != null ? nh : hosts.get(id);
+ }
+
+ /**
+ * Returns the device highlight for the given device identifier,
+ * or null if no match.
+ *
+ * @param id device identifier
+ * @return corresponding device highlight
+ */
+ public DeviceHighlight getDevice(String id) {
+ return devices.get(id);
+ }
+
+ /**
+ * Returns the host highlight for the given host identifier,
+ * or null if no match.
+ *
+ * @param id host identifier
+ * @return corresponding host highlight
+ */
+ public HostHighlight getHost(String id) {
+ return hosts.get(id);
+ }
+
+ /**
+ * Returns the link highlight for the given link identifier,
+ * or null if no match.
+ *
+ * @param id link identifier
+ * @return corresponding link highlight
+ */
+ public LinkHighlight getLink(String id) {
+ return links.get(id);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
new file mode 100644
index 00000000..76669a84
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ui.topo;
+
+/**
+ * Denotes the highlighting to apply to a host.
+ */
+public class HostHighlight extends NodeHighlight {
+
+ public HostHighlight(String hostId) {
+ super(TopoElementType.HOST, hostId);
+ }
+
+ // TODO: implement host highlighting:
+ // - visual highlight
+ // - badging
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
new file mode 100644
index 00000000..b4e43304
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.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.ui.topo;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Denotes the highlighting to be applied to a link.
+ * {@link Flavor} is a closed set of NO-, PRIMARY-, or SECONDARY- highlighting.
+ * {@link Mod} is an open ended set of additional modifications (CSS classes)
+ * that may also be applied.
+ * Note that {@link #MOD_OPTICAL} and {@link #MOD_ANIMATED} are pre-defined mods.
+ * Label text may be set, which will also be displayed on the link.
+ */
+public class LinkHighlight extends AbstractHighlight {
+
+ private static final String PLAIN = "plain";
+ private static final String PRIMARY = "primary";
+ private static final String SECONDARY = "secondary";
+ private static final String EMPTY = "";
+ private static final String SPACE = " ";
+
+ private final Flavor flavor;
+ private final Set<Mod> mods = new TreeSet<>();
+ private String label = EMPTY;
+
+ /**
+ * Constructs a link highlight entity.
+ *
+ * @param linkId the link identifier
+ * @param flavor the highlight flavor
+ */
+ public LinkHighlight(String linkId, Flavor flavor) {
+ super(TopoElementType.LINK, linkId);
+ this.flavor = checkNotNull(flavor);
+ }
+
+ /**
+ * Adds a highlighting modification to this link highlight.
+ *
+ * @param mod mod to be added
+ * @return self, for chaining
+ */
+ public LinkHighlight addMod(Mod mod) {
+ mods.add(checkNotNull(mod));
+ return this;
+ }
+
+ /**
+ * Adds a label to be displayed on the link.
+ *
+ * @param label the label text
+ * @return self, for chaining
+ */
+ public LinkHighlight setLabel(String label) {
+ this.label = label == null ? EMPTY : label;
+ return this;
+ }
+
+ /**
+ * Returns the highlight flavor.
+ *
+ * @return highlight flavor
+ */
+ public Flavor flavor() {
+ return flavor;
+ }
+
+ /**
+ * Returns the highlight modifications.
+ *
+ * @return highlight modifications
+ */
+ public Set<Mod> mods() {
+ return Collections.unmodifiableSet(mods);
+ }
+
+ /**
+ * Generates the CSS classes string from the {@link #flavor} and
+ * any optional {@link #mods}.
+ *
+ * @return CSS classes string
+ */
+ public String cssClasses() {
+ StringBuilder sb = new StringBuilder(flavor.toString());
+ mods.forEach(m -> sb.append(SPACE).append(m));
+ return sb.toString();
+ }
+
+ /**
+ * Returns the label text.
+ *
+ * @return label text
+ */
+ public String label() {
+ return label;
+ }
+
+ /**
+ * Link highlighting flavor.
+ */
+ public enum Flavor {
+ NO_HIGHLIGHT(PLAIN),
+ PRIMARY_HIGHLIGHT(PRIMARY),
+ SECONDARY_HIGHLIGHT(SECONDARY);
+
+ private String cssName;
+
+ Flavor(String s) {
+ cssName = s;
+ }
+
+ @Override
+ public String toString() {
+ return cssName;
+ }
+ }
+
+ /**
+ * Denotes a link to be tagged as an optical link.
+ */
+ public static final Mod MOD_OPTICAL = new Mod("optical");
+
+ /**
+ * Denotes a link to be tagged with animated traffic ("marching ants").
+ */
+ public static final Mod MOD_ANIMATED = new Mod("animated");
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java
new file mode 100644
index 00000000..d21a8724
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ui.topo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Highlighting modification.
+ * <p>
+ * Note that (for link highlights) this translates to a CSS class name
+ * that is applied to the link in the Topology UI.
+ */
+public final class Mod implements Comparable<Mod> {
+ private final String modId;
+
+ /**
+ * Constructs a mod with the given identifier.
+ *
+ * @param modId modification identifier
+ */
+ public Mod(String modId) {
+ this.modId = checkNotNull(modId);
+ }
+
+ @Override
+ public String toString() {
+ return modId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Mod mod = (Mod) o;
+ return modId.equals(mod.modId);
+ }
+
+ @Override
+ public int hashCode() {
+ return modId.hashCode();
+ }
+
+ @Override
+ public int compareTo(Mod o) {
+ return this.modId.compareTo(o.modId);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java
new file mode 100644
index 00000000..735f8166
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ui.topo;
+
+/**
+ * Parent class of {@link DeviceHighlight} and {@link HostHighlight}.
+ */
+public abstract class NodeHighlight extends AbstractHighlight {
+ public NodeHighlight(TopoElementType type, String elementId) {
+ super(type, elementId);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
new file mode 100644
index 00000000..b284de1b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
@@ -0,0 +1,252 @@
+/*
+ * 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.ui.topo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.Device;
+import org.onosproject.net.Element;
+import org.onosproject.net.Host;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.ui.JsonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.HostId.hostId;
+
+/**
+ * Encapsulates a selection of devices and/or hosts from the topology view.
+ */
+public class NodeSelection {
+
+ private static final Logger log =
+ LoggerFactory.getLogger(NodeSelection.class);
+
+ private static final String IDS = "ids";
+ private static final String HOVER = "hover";
+
+ private final DeviceService deviceService;
+ private final HostService hostService;
+
+ private final Set<String> ids;
+ private final String hover;
+
+ private final Set<Device> devices = new HashSet<>();
+ private final Set<Host> hosts = new HashSet<>();
+ private Element hovered;
+
+ /**
+ * Creates a node selection entity, from the given payload, using the
+ * supplied device and host services. Note that if a device or host was
+ * hovered over by the mouse, it is available via {@link #hovered()}.
+ *
+ * @param payload message payload
+ * @param deviceService device service
+ * @param hostService host service
+ */
+ public NodeSelection(ObjectNode payload,
+ DeviceService deviceService,
+ HostService hostService) {
+ this.deviceService = deviceService;
+ this.hostService = hostService;
+
+ ids = extractIds(payload);
+ hover = extractHover(payload);
+
+ // start by extracting the hovered element if any
+ if (isNullOrEmpty(hover)) {
+ hovered = null;
+ } else {
+ setHoveredElement();
+ }
+
+ // now go find the devices and hosts that are in the selection list
+ Set<String> unmatched = findDevices(ids);
+ unmatched = findHosts(unmatched);
+ if (unmatched.size() > 0) {
+ log.debug("Skipping unmatched IDs {}", unmatched);
+ }
+
+ }
+
+ /**
+ * Returns a view of the selected devices (hover not included).
+ *
+ * @return selected devices
+ */
+ public Set<Device> devices() {
+ return Collections.unmodifiableSet(devices);
+ }
+
+ /**
+ * Returns a view of the selected devices, including the hovered device
+ * if there was one.
+ *
+ * @return selected (plus hovered) devices
+ */
+ public Set<Device> devicesWithHover() {
+ Set<Device> withHover;
+ if (hovered != null && hovered instanceof Device) {
+ withHover = new HashSet<>(devices);
+ withHover.add((Device) hovered);
+ } else {
+ withHover = devices;
+ }
+ return Collections.unmodifiableSet(withHover);
+ }
+
+ /**
+ * Returns a view of the selected hosts (hover not included).
+ *
+ * @return selected hosts
+ */
+ public Set<Host> hosts() {
+ return Collections.unmodifiableSet(hosts);
+ }
+
+ /**
+ * Returns a view of the selected hosts, including the hovered host
+ * if thee was one.
+ *
+ * @return selected (plus hovered) hosts
+ */
+ public Set<Host> hostsWithHover() {
+ Set<Host> withHover;
+ if (hovered != null && hovered instanceof Host) {
+ withHover = new HashSet<>(hosts);
+ withHover.add((Host) hovered);
+ } else {
+ withHover = hosts;
+ }
+ return Collections.unmodifiableSet(withHover);
+ }
+
+ /**
+ * Returns the element (host or device) over which the mouse was hovering,
+ * or null.
+ *
+ * @return element hovered over
+ */
+ public Element hovered() {
+ return hovered;
+ }
+
+ /**
+ * Returns true if nothing is selected.
+ *
+ * @return true if nothing selected
+ */
+ public boolean none() {
+ return devices().size() == 0 && hosts().size() == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "NodeSelection{" +
+ "ids=" + ids +
+ ", hover='" + hover + '\'' +
+ ", #devices=" + devices.size() +
+ ", #hosts=" + hosts.size() +
+ '}';
+ }
+
+ // == helper methods
+
+ private Set<String> extractIds(ObjectNode payload) {
+ ArrayNode array = (ArrayNode) payload.path(IDS);
+ if (array == null || array.size() == 0) {
+ return Collections.emptySet();
+ }
+
+ Set<String> ids = new HashSet<>();
+ for (JsonNode node : array) {
+ ids.add(node.asText());
+ }
+ return ids;
+ }
+
+ private String extractHover(ObjectNode payload) {
+ return JsonUtils.string(payload, HOVER);
+ }
+
+ private void setHoveredElement() {
+ Set<String> unmatched;
+ unmatched = new HashSet<>();
+ unmatched.add(hover);
+ unmatched = findDevices(unmatched);
+ if (devices.size() == 1) {
+ hovered = devices.iterator().next();
+ devices.clear();
+ } else {
+ unmatched = findHosts(unmatched);
+ if (hosts.size() == 1) {
+ hovered = hosts.iterator().next();
+ hosts.clear();
+ } else {
+ hovered = null;
+ log.debug("Skipping unmatched HOVER {}", unmatched);
+ }
+ }
+ }
+
+ private Set<String> findDevices(Set<String> ids) {
+ Set<String> unmatched = new HashSet<>();
+ Device device;
+
+ for (String id : ids) {
+ try {
+ device = deviceService.getDevice(deviceId(id));
+ if (device != null) {
+ devices.add(device);
+ } else {
+ unmatched.add(id);
+ }
+ } catch (Exception e) {
+ unmatched.add(id);
+ }
+ }
+ return unmatched;
+ }
+
+ private Set<String> findHosts(Set<String> ids) {
+ Set<String> unmatched = new HashSet<>();
+ Host host;
+
+ for (String id : ids) {
+ try {
+ host = hostService.getHost(hostId(id));
+ if (host != null) {
+ hosts.add(host);
+ } else {
+ unmatched.add(id);
+ }
+ } catch (Exception e) {
+ unmatched.add(id);
+ }
+ }
+ return unmatched;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
new file mode 100644
index 00000000..121e0834
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
@@ -0,0 +1,353 @@
+/*
+ * 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.ui.topo;
+
+import com.google.common.collect.Sets;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Models a panel displayed on the Topology View.
+ */
+public class PropertyPanel {
+
+ private static final DecimalFormat DF0 = new DecimalFormat("#,###");
+
+ private String title;
+ private String typeId;
+ private String id;
+ private List<Prop> properties = new ArrayList<>();
+ private List<ButtonId> buttons = new ArrayList<>();
+
+ /**
+ * Constructs a property panel model with the given title and
+ * type identifier (icon to display).
+ *
+ * @param title title text
+ * @param typeId type (icon) ID
+ */
+ public PropertyPanel(String title, String typeId) {
+ this.title = title;
+ this.typeId = typeId;
+ }
+
+ /**
+ * Adds an ID field to the panel data, to be included in
+ * the returned JSON data to the client.
+ *
+ * @param id the identifier
+ * @return self, for chaining
+ */
+ public PropertyPanel id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, String value) {
+ properties.add(new Prop(key, value));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data, using a decimal formatter.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, int value) {
+ properties.add(new Prop(key, DF0.format(value)));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data, using a decimal formatter.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, long value) {
+ properties.add(new Prop(key, DF0.format(value)));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data. Note that the value's
+ * {@link Object#toString toString()} method is used to convert the
+ * value to a string.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, Object value) {
+ properties.add(new Prop(key, value.toString()));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data. Note that the value's
+ * {@link Object#toString toString()} method is used to convert the
+ * value to a string, from which the characters defined in the given
+ * regular expression string are stripped.
+ *
+ * @param key property key
+ * @param value property value
+ * @param reStrip regexp characters to strip from value string
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, Object value, String reStrip) {
+ String val = value.toString().replaceAll(reStrip, "");
+ properties.add(new Prop(key, val));
+ return this;
+ }
+
+ /**
+ * Adds a separator to the panel data.
+ *
+ * @return self, for chaining
+ */
+ public PropertyPanel addSeparator() {
+ properties.add(new Separator());
+ return this;
+ }
+
+ /**
+ * Returns the title text.
+ *
+ * @return title text
+ */
+ public String title() {
+ return title;
+ }
+
+ /**
+ * Returns the type identifier.
+ *
+ * @return type identifier
+ */
+ public String typeId() {
+ return typeId;
+ }
+
+ /**
+ * Returns the internal ID.
+ *
+ * @return the ID
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the list of properties to be displayed.
+ *
+ * @return the property list
+ */
+ // TODO: consider protecting this?
+ public List<Prop> properties() {
+ return properties;
+ }
+
+ /**
+ * Returns the list of button descriptors.
+ *
+ * @return the button list
+ */
+ // TODO: consider protecting this?
+ public List<ButtonId> buttons() {
+ return buttons;
+ }
+
+ // == MUTATORS
+
+ /**
+ * Sets the title text.
+ *
+ * @param title title text
+ * @return self, for chaining
+ */
+ public PropertyPanel title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * Sets the type identifier (icon ID).
+ *
+ * @param typeId type identifier
+ * @return self, for chaining
+ */
+ public PropertyPanel typeId(String typeId) {
+ this.typeId = typeId;
+ return this;
+ }
+
+ /**
+ * Removes properties with the given keys from the list.
+ *
+ * @param keys keys of properties to remove
+ * @return self, for chaining
+ */
+ public PropertyPanel removeProps(String... keys) {
+ Set<String> forRemoval = Sets.newHashSet(keys);
+ List<Prop> toKeep = new ArrayList<>();
+ for (Prop p: properties) {
+ if (!forRemoval.contains(p.key())) {
+ toKeep.add(p);
+ }
+ }
+ properties = toKeep;
+ return this;
+ }
+
+ /**
+ * Removes all currently defined properties.
+ *
+ * @return self, for chaining
+ */
+ public PropertyPanel removeAllProps() {
+ properties.clear();
+ return this;
+ }
+
+ /**
+ * Adds the given button descriptor to the panel data.
+ *
+ * @param button button descriptor
+ * @return self, for chaining
+ */
+ public PropertyPanel addButton(ButtonId button) {
+ buttons.add(button);
+ return this;
+ }
+
+ /**
+ * Removes buttons with the given descriptors from the list.
+ *
+ * @param descriptors descriptors to remove
+ * @return self, for chaining
+ */
+ public PropertyPanel removeButtons(ButtonId... descriptors) {
+ Set<ButtonId> forRemoval = Sets.newHashSet(descriptors);
+ List<ButtonId> toKeep = new ArrayList<>();
+ for (ButtonId bd: buttons) {
+ if (!forRemoval.contains(bd)) {
+ toKeep.add(bd);
+ }
+ }
+ buttons = toKeep;
+ return this;
+ }
+
+ /**
+ * Removes all currently defined buttons.
+ *
+ * @return self, for chaining
+ */
+ public PropertyPanel removeAllButtons() {
+ buttons.clear();
+ return this;
+ }
+
+ // ====================
+
+
+ /**
+ * Simple data carrier for a property, composed of a key/value pair.
+ */
+ public static class Prop {
+ private final String key;
+ private final String value;
+
+ /**
+ * Constructs a property data value.
+ *
+ * @param key property key
+ * @param value property value
+ */
+ public Prop(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Returns the property's key.
+ *
+ * @return the key
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns the property's value.
+ *
+ * @return the value
+ */
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Prop prop = (Prop) o;
+ return key.equals(prop.key) && value.equals(prop.value);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = key.hashCode();
+ result = 31 * result + value.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + key + " -> " + value + "}";
+ }
+ }
+
+ /**
+ * Auxiliary class representing a separator property.
+ */
+ public static class Separator extends Prop {
+ public Separator() {
+ super("-", "");
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
new file mode 100644
index 00000000..38a8f036
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
@@ -0,0 +1,129 @@
+/*
+ * 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.ui.topo;
+
+/**
+ * Defines string constants used in the Topology View of the ONOS GUI.
+ * <p>
+ * See also:
+ * <ul>
+ * <li> https://wiki.onosproject.org/display/ONOS/UI+Service+-+GlyphService </li>
+ * </ul>
+ */
+public final class TopoConstants {
+
+ /**
+ * Defines constants for standard glyph identifiers.
+ */
+ public static final class Glyphs {
+ public static final String UNKNOWN = "unknown";
+ public static final String BIRD = "bird";
+ public static final String NODE = "node";
+ public static final String SWITCH = "switch";
+ public static final String ROADM = "roadm";
+ public static final String ENDSTATION = "endstation";
+ public static final String ROUTER = "router";
+ public static final String BGP_SPEAKER = "bgpSpeaker";
+ public static final String CHAIN = "chain";
+ public static final String CROWN = "crown";
+ public static final String TOPO = "topo";
+ public static final String REFRESH = "refresh";
+ public static final String GARBAGE = "garbage";
+ public static final String FLOW_TABLE = "flowTable";
+ public static final String PORT_TABLE = "portTable";
+ public static final String GROUP_TABLE = "groupTable";
+ public static final String SUMMARY = "summary";
+ public static final String DETAILS = "details";
+ public static final String PORTS = "ports";
+ public static final String MAP = "map";
+ public static final String CYCLE_LABELS = "cycleLabels";
+ public static final String OBLIQUE = "oblique";
+ public static final String FILTERS = "filters";
+ public static final String RESET_ZOOM = "resetZoom";
+ public static final String RELATED_INTENTS = "relatedIntents";
+ public static final String NEXT_INTENT = "nextIntent";
+ public static final String PREV_INTENT = "prevIntent";
+ public static final String INTENT_TRAFFIC = "intentTraffic";
+ public static final String ALL_TRAFFIC = "allTraffic";
+ public static final String FLOWS = "flows";
+ public static final String EQ_MASTER = "eqMaster";
+ public static final String UI_ATTACHED = "uiAttached";
+ public static final String CHECK_MARK = "checkMark";
+ public static final String X_MARK = "xMark";
+ public static final String TRIANGLE_UP = "triangleUp";
+ public static final String TRIANGLE_DOWN = "triangleDown";
+ public static final String PLUS = "plus";
+ public static final String MINUS = "minus";
+ public static final String PLAY = "play";
+ public static final String STOP = "stop";
+ public static final String CLOUD = "cloud";
+ }
+
+ /**
+ * Defines constants for property names on the default summary and
+ * details panels.
+ */
+ public static final class Properties {
+ public static final String SEPARATOR = "-";
+
+ // summary panel
+ public static final String DEVICES = "Devices";
+ public static final String LINKS = "Links";
+ public static final String HOSTS = "Hosts";
+ public static final String TOPOLOGY_SSCS = "Topology SCCs";
+ public static final String INTENTS = "Intents";
+ public static final String TUNNELS = "Tunnels";
+ public static final String FLOWS = "Flows";
+ public static final String VERSION = "Version";
+
+ // device details
+ public static final String URI = "URI";
+ public static final String VENDOR = "Vendor";
+ public static final String HW_VERSION = "H/W Version";
+ public static final String SW_VERSION = "S/W Version";
+ public static final String SERIAL_NUMBER = "Serial Number";
+ public static final String PROTOCOL = "Protocol";
+ public static final String LATITUDE = "Latitude";
+ public static final String LONGITUDE = "Longitude";
+ public static final String PORTS = "Ports";
+
+ // host details
+ public static final String MAC = "MAC";
+ public static final String IP = "IP";
+ public static final String VLAN = "VLAN";
+ }
+
+ /**
+ * Defines identities of core buttons that appear on the topology
+ * details panel.
+ */
+ public static final class CoreButtons {
+ public static final ButtonId SHOW_DEVICE_VIEW =
+ new ButtonId("showDeviceView");
+
+ public static final ButtonId SHOW_FLOW_VIEW =
+ new ButtonId("showFlowView");
+
+ public static final ButtonId SHOW_PORT_VIEW =
+ new ButtonId("showPortView");
+
+ public static final ButtonId SHOW_GROUP_VIEW =
+ new ButtonId("showGroupView");
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java
new file mode 100644
index 00000000..dc327464
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.ui.topo;
+
+/**
+ * The topology element types to which a highlight can be applied.
+ */
+public enum TopoElementType {
+ DEVICE, HOST, LINK
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
new file mode 100644
index 00000000..a94068ee
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.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.ui.topo;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onosproject.ui.JsonUtils.envelope;
+
+/**
+ * JSON utilities for the Topology View.
+ */
+public final class TopoJson {
+ // package-private for unit test access
+ static final String SHOW_HIGHLIGHTS = "showHighlights";
+
+ static final String DEVICES = "devices";
+ static final String HOSTS = "hosts";
+ static final String LINKS = "links";
+ static final String SUBDUE = "subdue";
+
+ static final String ID = "id";
+ static final String LABEL = "label";
+ static final String CSS = "css";
+
+ static final String TITLE = "title";
+ static final String TYPE = "type";
+ static final String PROP_ORDER = "propOrder";
+ static final String PROPS = "props";
+ static final String BUTTONS = "buttons";
+
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private static ObjectNode objectNode() {
+ return MAPPER.createObjectNode();
+ }
+
+ private static ArrayNode arrayNode() {
+ return MAPPER.createArrayNode();
+ }
+
+ // non-instantiable
+ private TopoJson() { }
+
+ /**
+ * Returns a formatted message ready to send to the topology view
+ * to render highlights.
+ *
+ * @param highlights highlights model to transform
+ * @return fully formatted "show highlights" message
+ */
+ public static ObjectNode highlightsMessage(Highlights highlights) {
+ return envelope(SHOW_HIGHLIGHTS, json(highlights));
+ }
+
+ /**
+ * Transforms the given highlights model into a JSON message payload.
+ *
+ * @param highlights the model to transform
+ * @return JSON payload
+ */
+ public static ObjectNode json(Highlights highlights) {
+ ObjectNode payload = objectNode();
+
+ ArrayNode devices = arrayNode();
+ ArrayNode hosts = arrayNode();
+ ArrayNode links = arrayNode();
+
+ payload.set(DEVICES, devices);
+ payload.set(HOSTS, hosts);
+ payload.set(LINKS, links);
+
+ highlights.devices().forEach(dh -> devices.add(json(dh)));
+ highlights.hosts().forEach(hh -> hosts.add(json(hh)));
+ highlights.links().forEach(lh -> links.add(json(lh)));
+
+ Highlights.Amount toSubdue = highlights.subdueLevel();
+ if (!toSubdue.equals(Highlights.Amount.ZERO)) {
+ payload.put(SUBDUE, toSubdue.toString());
+ }
+ return payload;
+ }
+
+ private static ObjectNode json(DeviceHighlight dh) {
+ ObjectNode n = objectNode()
+ .put(ID, dh.elementId());
+ if (dh.subdued()) {
+ n.put(SUBDUE, true);
+ }
+ return n;
+ }
+
+ private static ObjectNode json(HostHighlight hh) {
+ ObjectNode n = objectNode()
+ .put(ID, hh.elementId());
+ if (hh.subdued()) {
+ n.put(SUBDUE, true);
+ }
+ return n;
+ }
+
+ private static ObjectNode json(LinkHighlight lh) {
+ ObjectNode n = objectNode()
+ .put(ID, lh.elementId())
+ .put(LABEL, lh.label())
+ .put(CSS, lh.cssClasses());
+ if (lh.subdued()) {
+ n.put(SUBDUE, true);
+ }
+ return n;
+ }
+
+ /**
+ * Translates the given property panel into JSON, for returning
+ * to the client.
+ *
+ * @param pp the property panel model
+ * @return JSON payload
+ */
+ public static ObjectNode json(PropertyPanel pp) {
+ ObjectNode result = objectNode()
+ .put(TITLE, pp.title())
+ .put(TYPE, pp.typeId())
+ .put(ID, pp.id());
+
+ ObjectNode pnode = objectNode();
+ ArrayNode porder = arrayNode();
+ for (PropertyPanel.Prop p : pp.properties()) {
+ porder.add(p.key());
+ pnode.put(p.key(), p.value());
+ }
+ result.set(PROP_ORDER, porder);
+ result.set(PROPS, pnode);
+
+ ArrayNode buttons = arrayNode();
+ for (ButtonId b : pp.buttons()) {
+ buttons.add(b.id());
+ }
+ result.set(BUTTONS, buttons);
+ return result;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
new file mode 100644
index 00000000..f92d5798
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
@@ -0,0 +1,159 @@
+/*
+ * 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.ui.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import java.text.DecimalFormat;
+
+import static org.onosproject.net.LinkKey.linkKey;
+
+/**
+ * Utility methods for helping out with formatting data for the Topology View
+ * in the web client.
+ */
+public final class TopoUtils {
+
+ // explicit decision made to not 'javadoc' these self explanatory constants
+ public static final double KILO = 1024;
+ public static final double MEGA = 1024 * KILO;
+ public static final double GIGA = 1024 * MEGA;
+
+ public static final String GBITS_UNIT = "Gb";
+ public static final String MBITS_UNIT = "Mb";
+ public static final String KBITS_UNIT = "Kb";
+ public static final String BITS_UNIT = "b";
+ public static final String GBYTES_UNIT = "GB";
+ public static final String MBYTES_UNIT = "MB";
+ public static final String KBYTES_UNIT = "KB";
+ public static final String BYTES_UNIT = "B";
+
+
+ private static final DecimalFormat DF2 = new DecimalFormat("#,###.##");
+
+ private static final String COMPACT = "%s/%s-%s/%s";
+ private static final String EMPTY = "";
+ private static final String SPACE = " ";
+ private static final String PER_SEC = "ps";
+ private static final String FLOW = "flow";
+ private static final String FLOWS = "flows";
+
+ // non-instantiable
+ private TopoUtils() { }
+
+ /**
+ * Returns a compact identity for the given link, in the form
+ * used to identify links in the Topology View on the client.
+ *
+ * @param link link
+ * @return compact link identity
+ */
+ public static String compactLinkString(Link link) {
+ return String.format(COMPACT, link.src().elementId(), link.src().port(),
+ link.dst().elementId(), link.dst().port());
+ }
+
+ /**
+ * Produces a canonical link key, that is, one that will match both a link
+ * and its inverse.
+ *
+ * @param link the link
+ * @return canonical key
+ */
+ public static LinkKey canonicalLinkKey(Link link) {
+ String sn = link.src().elementId().toString();
+ String dn = link.dst().elementId().toString();
+ return sn.compareTo(dn) < 0 ?
+ linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
+ }
+
+ /**
+ * Returns human readable count of bytes, to be displayed as a label.
+ *
+ * @param bytes number of bytes
+ * @return formatted byte count
+ */
+ public static String formatBytes(long bytes) {
+ String unit;
+ double value;
+ if (bytes > GIGA) {
+ value = bytes / GIGA;
+ unit = GBYTES_UNIT;
+ } else if (bytes > MEGA) {
+ value = bytes / MEGA;
+ unit = MBYTES_UNIT;
+ } else if (bytes > KILO) {
+ value = bytes / KILO;
+ unit = KBYTES_UNIT;
+ } else {
+ value = bytes;
+ unit = BYTES_UNIT;
+ }
+ return DF2.format(value) + SPACE + unit;
+ }
+
+ /**
+ * Returns human readable bit rate, to be displayed as a label.
+ *
+ * @param bytes bytes per second
+ * @return formatted bits per second
+ */
+ public static String formatBitRate(long bytes) {
+ String unit;
+ double value;
+
+ //Convert to bits
+ long bits = bytes * 8;
+ if (bits > GIGA) {
+ value = bits / GIGA;
+ unit = GBITS_UNIT;
+
+ // NOTE: temporary hack to clip rate at 10.0 Gbps
+ // Added for the CORD Fabric demo at ONS 2015
+ // TODO: provide a more elegant solution to this issue
+ if (value > 10.0) {
+ value = 10.0;
+ }
+
+ } else if (bits > MEGA) {
+ value = bits / MEGA;
+ unit = MBITS_UNIT;
+ } else if (bits > KILO) {
+ value = bits / KILO;
+ unit = KBITS_UNIT;
+ } else {
+ value = bits;
+ unit = BITS_UNIT;
+ }
+ return DF2.format(value) + SPACE + unit + PER_SEC;
+ }
+
+ /**
+ * Returns human readable flow count, to be displayed as a label.
+ *
+ * @param flows number of flows
+ * @return formatted flow count
+ */
+ public static String formatFlows(long flows) {
+ if (flows < 1) {
+ return EMPTY;
+ }
+ return String.valueOf(flows) + SPACE + (flows > 1 ? FLOWS : FLOW);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java
new file mode 100644
index 00000000..85ac7fea
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Mechanism for dynamically extending topology view with information and
+ * behaviour overlays.
+ */
+package org.onosproject.ui.topo; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/flow/doc-files/flow-design.png b/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/flow/doc-files/flow-design.png
new file mode 100644
index 00000000..e08745d1
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/flow/doc-files/flow-design.png
Binary files differ
diff --git a/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-design.png b/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-design.png
new file mode 100644
index 00000000..4392ad45
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-design.png
Binary files differ
diff --git a/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-states.png b/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-states.png
new file mode 100644
index 00000000..32804191
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/javadoc/org/onosproject/net/intent/doc-files/intent-states.png
Binary files differ
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/TestApplicationId.java b/framework/src/onos/core/api/src/test/java/org/onosproject/TestApplicationId.java
new file mode 100644
index 00000000..a57d5e8a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/TestApplicationId.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;
+
+import org.onosproject.core.ApplicationId;
+
+import java.util.Objects;
+
+/**
+ * Test application ID.
+ */
+public class TestApplicationId implements ApplicationId {
+
+ private final String name;
+ private final short id;
+
+ public TestApplicationId(String name) {
+ this.name = name;
+ this.id = (short) Objects.hash(name);
+ }
+
+ public static ApplicationId create(String name) {
+ return new TestApplicationId(name);
+ }
+
+ @Override
+ public short id() {
+ return id;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/VersionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/VersionTest.java
new file mode 100644
index 00000000..ecf5f34e
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/VersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.core.Version;
+
+import static org.junit.Assert.*;
+import static org.onosproject.core.Version.version;
+
+/**
+ * Tests of the version descriptor.
+ */
+public class VersionTest {
+
+ @Test
+ public void fromParts() {
+ Version v = version(1, 2, "3", "4321");
+ assertEquals("wrong major", 1, v.major());
+ assertEquals("wrong minor", 2, v.minor());
+ assertEquals("wrong patch", "3", v.patch());
+ assertEquals("wrong build", "4321", v.build());
+ }
+
+ @Test
+ public void fromString() {
+ Version v = version("1.2.3.4321");
+ assertEquals("wrong major", 1, v.major());
+ assertEquals("wrong minor", 2, v.minor());
+ assertEquals("wrong patch", "3", v.patch());
+ assertEquals("wrong build", "4321", v.build());
+ }
+
+ @Test
+ public void snapshot() {
+ Version v = version("1.2.3-SNAPSHOT");
+ assertEquals("wrong major", 1, v.major());
+ assertEquals("wrong minor", 2, v.minor());
+ assertEquals("wrong patch", "3", v.patch());
+ assertEquals("wrong build", "SNAPSHOT", v.build());
+ }
+
+ @Test
+ public void shortNumber() {
+ Version v = version("1.2.3");
+ assertEquals("wrong major", 1, v.major());
+ assertEquals("wrong minor", 2, v.minor());
+ assertEquals("wrong patch", "3", v.patch());
+ assertEquals("wrong build", null, v.build());
+ }
+
+ @Test
+ public void minimal() {
+ Version v = version("1.4");
+ assertEquals("wrong major", 1, v.major());
+ assertEquals("wrong minor", 4, v.minor());
+ assertEquals("wrong patch", null, v.patch());
+ assertEquals("wrong build", null, v.build());
+ }
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(version("1.2.3.4321"), version(1, 2, "3", "4321"))
+ .addEqualityGroup(version("1.9.3.4321"), version(1, 9, "3", "4321"))
+ .addEqualityGroup(version("1.2.8.4321"), version(1, 2, "8", "4321"))
+ .addEqualityGroup(version("1.2.3.x"), version(1, 2, "3", "x"))
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java
new file mode 100644
index 00000000..edcc2094
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Adapter for testing against application admin service.
+ */
+public class ApplicationAdminServiceAdapter extends ApplicationServiceAdapter
+ implements ApplicationAdminService {
+ @Override
+ public Set<Application> getApplications() {
+ return null;
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public Set<Permission> getPermissions(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public void addListener(ApplicationListener listener) {
+ }
+
+ @Override
+ public void removeListener(ApplicationListener listener) {
+ }
+
+ @Override
+ public Application install(InputStream appDescStream) {
+ return null;
+ }
+
+ @Override
+ public void uninstall(ApplicationId appId) {
+ }
+
+ @Override
+ public void activate(ApplicationId appId) {
+ }
+
+ @Override
+ public void deactivate(ApplicationId appId) {
+ }
+
+ @Override
+ public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
new file mode 100644
index 00000000..d31cc268
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.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.app;
+
+import org.junit.Test;
+import org.onosproject.core.Application;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.event.AbstractEventTest;
+
+import java.util.Optional;
+
+import static org.onosproject.app.ApplicationEvent.Type.APP_ACTIVATED;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+import static org.onosproject.core.DefaultApplicationTest.APP_ID;
+
+/**
+ * Test of the application event.
+ */
+public class ApplicationEventTest extends AbstractEventTest {
+
+ private Application createApp() {
+ return new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
+ PERMS, Optional.of(FURL), FEATURES);
+ }
+
+ @Test
+ public void withoutTime() {
+ Application app = createApp();
+ ApplicationEvent event = new ApplicationEvent(APP_ACTIVATED, app, 123L);
+ validateEvent(event, APP_ACTIVATED, app, 123L);
+ }
+
+ @Test
+ public void withTime() {
+ Application app = createApp();
+ long before = System.currentTimeMillis();
+ ApplicationEvent event = new ApplicationEvent(APP_ACTIVATED, app);
+ long after = System.currentTimeMillis();
+ validateEvent(event, APP_ACTIVATED, app, before, after);
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java
new file mode 100644
index 00000000..a0c7ef1c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.onlab.junit.ExceptionTest;
+
+public class ApplicationExceptionTest extends ExceptionTest {
+
+ @Override
+ protected Exception getDefault() {
+ return new ApplicationException();
+ }
+
+ @Override
+ protected Exception getWithMessage() {
+ return new ApplicationException(MESSAGE);
+ }
+
+ @Override
+ protected Exception getWithMessageAndCause() {
+ return new ApplicationException(MESSAGE, CAUSE);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java
new file mode 100644
index 00000000..479cc59a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+
+import java.util.Set;
+
+/**
+ * Adapter for testing against application service.
+ */
+public class ApplicationServiceAdapter implements ApplicationService {
+ @Override
+ public Set<Application> getApplications() {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getId(String name) {
+ return null;
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public Set<Permission> getPermissions(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public void addListener(ApplicationListener listener) {
+ }
+
+ @Override
+ public void removeListener(ApplicationListener listener) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java
new file mode 100644
index 00000000..1a9ad8d2
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+import org.onosproject.store.AbstractStore;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Adapter for application testing against application store.
+ */
+public class ApplicationStoreAdapter
+ extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate>
+ implements ApplicationStore {
+ @Override
+ public Set<Application> getApplications() {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getId(String name) {
+ return null;
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public Application create(InputStream appDescStream) {
+ return null;
+ }
+
+ @Override
+ public void remove(ApplicationId appId) {
+ }
+
+ @Override
+ public void activate(ApplicationId appId) {
+ }
+
+ @Override
+ public void deactivate(ApplicationId appId) {
+ }
+
+ @Override
+ public Set<Permission> getPermissions(ApplicationId appId) {
+ return null;
+ }
+
+ @Override
+ public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
new file mode 100644
index 00000000..d40d3fea
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.Version;
+import org.onosproject.security.AppPermission;
+import org.onosproject.security.Permission;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Basic tests of the default app description.
+ */
+public class DefaultApplicationDescriptionTest {
+
+ public static final String APP_NAME = "org.foo.app";
+ public static final Version VER = Version.version(1, 2, "a", null);
+ public static final String DESC = "Awesome application from Circus, Inc.";
+ public static final String ORIGIN = "Circus";
+ public static final ApplicationRole ROLE = ApplicationRole.ADMIN;
+ public static final Set<Permission> PERMS = ImmutableSet.of(
+ new Permission(AppPermission.class.getName(), "FLOWRULE_WRITE"),
+ new Permission(AppPermission.class.getName(), "FLOWRULE_READ"));
+ public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
+ public static final List<String> FEATURES = ImmutableList.of("foo", "bar");
+
+ @Test
+ public void basics() {
+ ApplicationDescription app =
+ new DefaultApplicationDescription(APP_NAME, VER, DESC, ORIGIN,
+ ROLE, PERMS, FURL, FEATURES);
+ assertEquals("incorrect id", APP_NAME, app.name());
+ assertEquals("incorrect version", VER, app.version());
+ assertEquals("incorrect description", DESC, app.description());
+ assertEquals("incorrect origin", ORIGIN, app.origin());
+ assertEquals("incorect role", ROLE, app.role());
+ assertEquals("incorrect permissions", PERMS, app.permissions());
+ assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
+ assertEquals("incorrect features", FEATURES, app.features());
+ assertTrue("incorrect toString", app.toString().contains(APP_NAME));
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ComponentConfigAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ComponentConfigAdapter.java
new file mode 100644
index 00000000..a1abd188
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ComponentConfigAdapter.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+/**
+ * Adapter for testing against component configuration service.
+ */
+public class ComponentConfigAdapter implements ComponentConfigService {
+ @Override
+ public Set<String> getComponentNames() {
+ return null;
+ }
+
+ @Override
+ public void registerProperties(Class<?> componentClass) {
+
+ }
+
+ @Override
+ public void unregisterProperties(Class<?> componentClass, boolean clear) {
+
+ }
+
+ @Override
+ public Set<ConfigProperty> getProperties(String componentName) {
+ return null;
+ }
+
+ @Override
+ public void setProperty(String componentName, String name, String value) {
+
+ }
+
+ @Override
+ public void unsetProperty(String componentName, String name) {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ConfigPropertyTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ConfigPropertyTest.java
new file mode 100644
index 00000000..b4ba8634
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cfg/ConfigPropertyTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.cfg.ConfigProperty.Type;
+
+import static org.junit.Assert.*;
+import static org.onosproject.cfg.ConfigProperty.Type.*;
+import static org.onosproject.cfg.ConfigProperty.defineProperty;
+import static org.onosproject.cfg.ConfigProperty.resetProperty;
+import static org.onosproject.cfg.ConfigProperty.setProperty;
+
+/**
+ * Set of tests of the configuration property class.
+ */
+public class ConfigPropertyTest {
+
+ @Test
+ public void basics() {
+ ConfigProperty p = defineProperty("foo", STRING, "bar", "Foo Prop");
+ validate(p, "foo", STRING, "bar", "bar");
+ p = setProperty(p, "BAR");
+ validate(p, "foo", STRING, "BAR", "bar");
+ p = resetProperty(p);
+ validate(p, "foo", STRING, "bar", "bar");
+ }
+
+ @Test
+ public void equality() {
+ new EqualsTester()
+ .addEqualityGroup(defineProperty("foo", STRING, "bar", "Desc"),
+ defineProperty("foo", STRING, "goo", "Desc"))
+ .addEqualityGroup(defineProperty("bar", STRING, "bar", "Desc"),
+ defineProperty("bar", STRING, "goo", "Desc"))
+ .testEquals();
+ }
+
+ private void validate(ConfigProperty p, String name, Type type, String v, String dv) {
+ assertEquals("incorrect name", name, p.name());
+ assertEquals("incorrect type", type, p.type());
+ assertEquals("incorrect value", v, p.value());
+ assertEquals("incorrect default", dv, p.defaultValue());
+ assertEquals("incorrect description", "Foo Prop", p.description());
+ }
+
+ @Test
+ public void asInteger() {
+ ConfigProperty p = defineProperty("foo", INTEGER, "123", "Foo Prop");
+ validate(p, "foo", INTEGER, "123", "123");
+ assertEquals("incorrect value", 123, p.asInteger());
+ assertEquals("incorrect value", 123L, p.asLong());
+ }
+
+ @Test
+ public void asLong() {
+ ConfigProperty p = defineProperty("foo", LONG, "123", "Foo Prop");
+ validate(p, "foo", LONG, "123", "123");
+ assertEquals("incorrect value", 123L, p.asLong());
+ }
+
+ @Test
+ public void asFloat() {
+ ConfigProperty p = defineProperty("foo", FLOAT, "123.0", "Foo Prop");
+ validate(p, "foo", FLOAT, "123.0", "123.0");
+ assertEquals("incorrect value", 123.0, p.asFloat(), 0.01);
+ assertEquals("incorrect value", 123.0, p.asDouble(), 0.01);
+ }
+
+ @Test
+ public void asDouble() {
+ ConfigProperty p = defineProperty("foo", DOUBLE, "123.0", "Foo Prop");
+ validate(p, "foo", DOUBLE, "123.0", "123.0");
+ assertEquals("incorrect value", 123.0, p.asDouble(), 0.01);
+ }
+
+ @Test
+ public void asBoolean() {
+ ConfigProperty p = defineProperty("foo", BOOLEAN, "true", "Foo Prop");
+ validate(p, "foo", BOOLEAN, "true", "true");
+ assertEquals("incorrect value", true, p.asBoolean());
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java
new file mode 100644
index 00000000..b88b5ff6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Test adapter for the cluster service.
+ */
+public class ClusterServiceAdapter implements ClusterService {
+ ControllerNode local = new DefaultControllerNode(new NodeId("local"),
+ IpAddress.valueOf("127.0.0.1"));
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return local;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return ImmutableSet.of(local);
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ return null;
+ }
+
+ @Override
+ public ControllerNode.State getState(NodeId nodeId) {
+ return null;
+ }
+
+ @Override
+ public DateTime getLastUpdated(NodeId nodeId) {
+ return null;
+ }
+
+ @Override
+ public void addListener(ClusterEventListener listener) {
+ }
+
+ @Override
+ public void removeListener(ClusterEventListener listener) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ControllerNodeToNodeIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ControllerNodeToNodeIdTest.java
new file mode 100644
index 00000000..0b4d1ef6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/ControllerNodeToNodeIdTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cluster;
+
+import static com.google.common.base.Predicates.notNull;
+import static org.junit.Assert.*;
+import static org.onosproject.cluster.ControllerNodeToNodeId.toNodeId;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+
+import com.google.common.collect.FluentIterable;
+
+
+public class ControllerNodeToNodeIdTest {
+
+ private static final NodeId NID1 = new NodeId("foo");
+ private static final NodeId NID2 = new NodeId("bar");
+ private static final NodeId NID3 = new NodeId("buz");
+
+ private static final IpAddress IP1 = IpAddress.valueOf("127.0.0.1");
+ private static final IpAddress IP2 = IpAddress.valueOf("127.0.0.2");
+ private static final IpAddress IP3 = IpAddress.valueOf("127.0.0.3");
+
+ private static final ControllerNode CN1 = new DefaultControllerNode(NID1, IP1);
+ private static final ControllerNode CN2 = new DefaultControllerNode(NID2, IP2);
+ private static final ControllerNode CN3 = new DefaultControllerNode(NID3, IP3);
+
+
+ @Test
+ public final void testToNodeId() {
+
+ final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3, null);
+ final List<NodeId> nodeIds = Arrays.asList(NID1, NID2, NID3);
+
+ assertEquals(nodeIds,
+ FluentIterable.from(nodes)
+ .transform(toNodeId())
+ .filter(notNull())
+ .toList());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipEventTest.java
new file mode 100644
index 00000000..be0321bf
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipEventTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.cluster;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the leadership event test.
+ */
+public class LeadershipEventTest {
+ private final NodeId node1 = new NodeId("1");
+ private final NodeId node2 = new NodeId("2");
+ private final Leadership lead1 = new Leadership("topic1", node1, 1L, 2L);
+ private final Leadership lead2 = new Leadership("topic1", node2, 1L, 2L);
+ private final LeadershipEvent event1 =
+ new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, lead1);
+ private final long time = System.currentTimeMillis();
+ private final LeadershipEvent event2 =
+ new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
+ lead2, time);
+ private final LeadershipEvent sameAsEvent2 =
+ new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
+ lead2, time);
+ private final LeadershipEvent event3 =
+ new LeadershipEvent(LeadershipEvent.Type.LEADER_BOOTED, lead1);
+ private final LeadershipEvent event4 =
+ new LeadershipEvent(LeadershipEvent.Type.LEADER_REELECTED, lead1);
+ private final LeadershipEvent event5 =
+ new LeadershipEvent(LeadershipEvent.Type.LEADER_REELECTED, lead2);
+
+ /**
+ * Tests for proper operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(event1)
+ .addEqualityGroup(event2, sameAsEvent2)
+ .addEqualityGroup(event3)
+ .addEqualityGroup(event4)
+ .addEqualityGroup(event5)
+ .testEquals();
+ }
+
+ /**
+ * Tests that objects are created properly.
+ */
+ @Test
+ public void checkConstruction() {
+ assertThat(event1.type(), is(LeadershipEvent.Type.LEADER_ELECTED));
+ assertThat(event1.subject(), is(lead1));
+
+ assertThat(event2.time(), is(time));
+ assertThat(event2.type(), is(LeadershipEvent.Type.CANDIDATES_CHANGED));
+ assertThat(event2.subject(), is(lead2));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipServiceAdapter.java
new file mode 100644
index 00000000..e1d421d0
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipServiceAdapter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.cluster;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Test adapter for leadership service.
+ */
+public class LeadershipServiceAdapter implements LeadershipService {
+
+ @Override
+ public NodeId getLeader(String path) {
+ return null;
+ }
+
+ @Override
+ public Leadership getLeadership(String path) {
+ return null;
+ }
+
+ @Override
+ public Set<String> ownedTopics(NodeId nodeId) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Leadership> runForLeadership(String path) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> withdraw(String path) {
+ return null;
+ }
+
+ @Override
+ public Map<String, Leadership> getLeaderBoard() {
+ return null;
+ }
+
+ @Override
+ public void addListener(LeadershipEventListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(LeadershipEventListener listener) {
+
+ }
+
+ @Override
+ public Map<String, List<NodeId>> getCandidates() {
+ return null;
+ }
+
+ @Override
+ public List<NodeId> getCandidates(String path) {
+ return null;
+ }
+
+ @Override
+ public boolean stepdown(String path) {
+ return false;
+ }
+
+ @Override
+ public boolean makeTopCandidate(String path, NodeId nodeId) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipTest.java
new file mode 100644
index 00000000..e2a86587
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/LeadershipTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.cluster;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the Leadership class.
+ */
+public class LeadershipTest {
+ private final NodeId node1 = new NodeId("1");
+ private final NodeId node2 = new NodeId("2");
+ private final Leadership lead1 = new Leadership("topic1", node1, 1L, 2L);
+ private final Leadership sameAsLead1 = new Leadership("topic1", node1, 1L, 2L);
+ private final Leadership lead2 = new Leadership("topic2", node1, 1L, 2L);
+ private final Leadership lead3 = new Leadership("topic1", node1, 2L, 2L);
+ private final Leadership lead4 = new Leadership("topic1", node1, 3L, 2L);
+ private final Leadership lead5 = new Leadership("topic1", node1, 3L, 3L);
+ private final Leadership lead6 = new Leadership("topic1", node1,
+ ImmutableList.of(node2), 1L, 2L);
+ private final Leadership lead7 = new Leadership("topic1",
+ ImmutableList.of(node2), 1L, 2L);
+
+ /**
+ * Tests for proper operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(lead1, sameAsLead1)
+ .addEqualityGroup(lead2)
+ .addEqualityGroup(lead3)
+ .addEqualityGroup(lead4)
+ .addEqualityGroup(lead5)
+ .addEqualityGroup(lead6)
+ .addEqualityGroup(lead7)
+ .testEquals();
+ }
+
+ /**
+ * Tests that objects are created properly and accessor methods return
+ * the correct vsalues.
+ */
+ @Test
+ public void checkConstruction() {
+ assertThat(lead6.electedTime(), is(2L));
+ assertThat(lead6.epoch(), is(1L));
+ assertThat(lead6.leader(), is(node1));
+ assertThat(lead6.topic(), is("topic1"));
+ assertThat(lead6.candidates(), hasSize(1));
+ assertThat(lead6.candidates(), contains(node2));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java
new file mode 100644
index 00000000..4998bf5a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test to check behavioral correctness of the RoleInfo structure.
+ */
+public class RoleInfoTest {
+ private static final NodeId N1 = new NodeId("n1");
+ private static final NodeId N2 = new NodeId("n2");
+ private static final NodeId N3 = new NodeId("n3");
+ private static final NodeId N4 = new NodeId("n4");
+
+ private static final List<NodeId> BKUP1 = Lists.newArrayList(N2, N3);
+ private static final List<NodeId> BKUP2 = Lists.newArrayList(N3, N4);
+
+ private static final RoleInfo RI1 = new RoleInfo(N1, BKUP1);
+ private static final RoleInfo RI2 = new RoleInfo(N1, BKUP2);
+ private static final RoleInfo RI3 = new RoleInfo(N2, BKUP1);
+ private static final RoleInfo RI4 = new RoleInfo(null, BKUP2);
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(RI1, new RoleInfo(new NodeId("n1"), Lists.newArrayList(N2, N3)))
+ .addEqualityGroup(RI3);
+ }
+
+ @Test
+ public void basics() {
+ assertEquals("wrong master", new NodeId("n1"), RI1.master());
+ assertEquals("wrong Backups", RI1.backups(), Lists.newArrayList(N2, N3));
+ assertEquals("wrong empty master", RI4.master(), null);
+
+ List<NodeId> bkup3 = Lists.newArrayList(N3, new NodeId("n4"));
+ assertEquals("equals() broken", new RoleInfo(N1, bkup3), RI2);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/codec/JsonCodecTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/codec/JsonCodecTest.java
new file mode 100644
index 00000000..eb04d9a5
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/codec/JsonCodecTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.codec;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the base JSON codec abstraction.
+ */
+public class JsonCodecTest {
+
+ private static class Foo {
+ final String name;
+
+ Foo(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Foo other = (Foo) obj;
+ return Objects.equals(this.name, other.name);
+ }
+ }
+
+ private static class FooCodec extends JsonCodec<Foo> {
+ @Override
+ public ObjectNode encode(Foo entity, CodecContext context) {
+ return context.mapper().createObjectNode().put("name", entity.name);
+ }
+
+ @Override
+ public Foo decode(ObjectNode json, CodecContext context) {
+ return new Foo(json.get("name").asText());
+ }
+ }
+
+ @Test
+ public void encode() {
+ Foo f1 = new Foo("foo");
+ Foo f2 = new Foo("bar");
+ FooCodec codec = new FooCodec();
+ ImmutableList<Foo> entities = ImmutableList.of(f1, f2);
+ ArrayNode json = codec.encode(entities, new TestContext());
+ List<Foo> foos = codec.decode(json, new TestContext());
+ assertEquals("incorrect encode/decode", entities, foos);
+ }
+
+ private class TestContext implements CodecContext {
+ private ObjectMapper mapper = new ObjectMapper();
+ @Override
+ public ObjectMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ public <T> JsonCodec<T> codec(Class<T> entityClass) {
+ return null;
+ }
+
+ @Override
+ public <T> T getService(Class<T> serviceClass) {
+ return null;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java
new file mode 100644
index 00000000..c3d0f237
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.core;
+
+import java.util.Set;
+
+/**
+ * Adapter for testing against app id store.
+ */
+public class ApplicationIdStoreAdapter implements ApplicationIdStore {
+ @Override
+ public Set<ApplicationId> getAppIds() {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ return null;
+ }
+
+ @Override
+ public ApplicationId registerApplication(String identifier) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/core/CoreServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/core/CoreServiceAdapter.java
new file mode 100644
index 00000000..0f6abd68
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/core/CoreServiceAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.core;
+
+import java.util.Set;
+
+/**
+ * Test adapter for core service.
+ */
+public class CoreServiceAdapter implements CoreService {
+ @Override
+ public Version version() {
+ return null;
+ }
+
+ @Override
+ public Set<ApplicationId> getAppIds() {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ return null;
+ }
+
+ @Override
+ public ApplicationId registerApplication(String identifier) {
+ return null;
+ }
+
+ @Override
+ public IdGenerator getIdGenerator(String topic) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
new file mode 100644
index 00000000..cbedb79c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.core;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+
+/**
+ * Basic tests of the default app descriptor.
+ */
+public class DefaultApplicationTest {
+
+ public static final ApplicationId APP_ID = new DefaultApplicationId(2, APP_NAME);
+
+ @Test
+ public void basics() {
+ Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
+ PERMS, Optional.of(FURL), FEATURES);
+ assertEquals("incorrect id", APP_ID, app.id());
+ assertEquals("incorrect version", VER, app.version());
+ assertEquals("incorrect description", DESC, app.description());
+ assertEquals("incorrect origin", ORIGIN, app.origin());
+ assertEquals("incorrect role", ROLE, app.role());
+ assertEquals("incorrect permissions", PERMS, app.permissions());
+ assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
+ assertEquals("incorrect features", FEATURES, app.features());
+ assertTrue("incorrect toString", app.toString().contains(APP_NAME));
+ }
+
+ @Test
+ public void testEquality() {
+ Application a1 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
+ PERMS, Optional.of(FURL), FEATURES);
+ Application a2 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
+ PERMS, Optional.of(FURL), FEATURES);
+ Application a3 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
+ PERMS, Optional.empty(), FEATURES);
+ Application a4 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN + "asd", ROLE,
+ PERMS, Optional.of(FURL), FEATURES);
+ new EqualsTester().addEqualityGroup(a1, a2)
+ .addEqualityGroup(a3).addEqualityGroup(a4).testEquals();
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultGroupIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultGroupIdTest.java
new file mode 100644
index 00000000..21dea86b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultGroupIdTest.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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+/**
+ * Test for DefaultGroupId.
+ */
+public class DefaultGroupIdTest {
+
+ /**
+ * Tests the equality of the instances.
+ */
+ @Test
+ public void testEquality() {
+ DefaultGroupId id1 = new DefaultGroupId((short) 1);
+ DefaultGroupId id2 = new DefaultGroupId((short) 1);
+ DefaultGroupId id3 = new DefaultGroupId((short) 2);
+
+ new EqualsTester()
+ .addEqualityGroup(id1, id2)
+ .addEqualityGroup(id3)
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java
new file mode 100644
index 00000000..ac565d80
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.core;
+
+import org.onlab.junit.ExceptionTest;
+
+public class UnavailableIdExceptionTest extends ExceptionTest {
+
+ @Override
+ protected Exception getDefault() {
+ return new UnavailableIdException();
+ }
+
+ @Override
+ protected Exception getWithMessage() {
+ return new UnavailableIdException(MESSAGE);
+ }
+
+ @Override
+ protected Exception getWithMessageAndCause() {
+ return new UnavailableIdException(MESSAGE, CAUSE);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/AbstractEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/AbstractEventTest.java
new file mode 100644
index 00000000..c66c4b84
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/AbstractEventTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.event.TestEvent.Type.FOO;
+
+/**
+ * Tests of the base event abstraction.
+ */
+public class AbstractEventTest {
+
+ /**
+ * Validates the base attributes of an event.
+ *
+ * @param event event to validate
+ * @param type event type
+ * @param subject event subject
+ * @param time event time
+ * @param <T> type of event
+ * @param <S> type of subject
+ */
+ protected static <T extends Enum, S>
+ void validateEvent(Event<T, S> event, T type, S subject, long time) {
+ assertEquals("incorrect type", type, event.type());
+ assertEquals("incorrect subject", subject, event.subject());
+ assertEquals("incorrect time", time, event.time());
+ }
+
+ /**
+ * Validates the base attributes of an event.
+ *
+ * @param event event to validate
+ * @param type event type
+ * @param subject event subject
+ * @param minTime minimum event time inclusive
+ * @param maxTime maximum event time inclusive
+ * @param <T> type of event
+ * @param <S> type of subject
+ */
+ protected static <T extends Enum, S>
+ void validateEvent(Event<T, S> event, T type, S subject,
+ long minTime, long maxTime) {
+ assertEquals("incorrect type", type, event.type());
+ assertEquals("incorrect subject", subject, event.subject());
+ assertTrue("incorrect time", minTime <= event.time() && event.time() <= maxTime);
+ }
+
+ @Test
+ public void withTime() {
+ TestEvent event = new TestEvent(FOO, "foo", 123L);
+ validateEvent(event, FOO, "foo", 123L);
+ }
+
+ @Test
+ public void withoutTime() {
+ long before = System.currentTimeMillis();
+ TestEvent event = new TestEvent(FOO, "foo");
+ long after = System.currentTimeMillis();
+ validateEvent(event, FOO, "foo", before, after);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/BrokenListener.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/BrokenListener.java
new file mode 100644
index 00000000..1a013ada
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/BrokenListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Test event listener fixture.
+ */
+public class BrokenListener extends TestListener {
+
+ public void event(TestEvent event) {
+ throw new IllegalStateException("boom");
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/DefaultEventSinkRegistryTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/DefaultEventSinkRegistryTest.java
new file mode 100644
index 00000000..2e24228a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/DefaultEventSinkRegistryTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests of the default event sink registry.
+ */
+public class DefaultEventSinkRegistryTest {
+
+ private DefaultEventSinkRegistry registry;
+
+ private static class FooEvent extends TestEvent {
+ public FooEvent(String subject) {
+ super(Type.FOO, subject);
+ }
+ }
+
+ private static class BarEvent extends TestEvent {
+ public BarEvent(String subject) {
+ super(Type.BAR, subject);
+ }
+ }
+
+ private static class FooSink implements EventSink<FooEvent> {
+ @Override public void process(FooEvent event) {}
+ }
+
+ private static class BarSink implements EventSink<BarEvent> {
+ @Override public void process(BarEvent event) {}
+ }
+
+ @Before
+ public void setUp() {
+ registry = new DefaultEventSinkRegistry();
+ }
+
+ @Test
+ public void basics() {
+ FooSink fooSink = new FooSink();
+ BarSink barSink = new BarSink();
+ registry.addSink(FooEvent.class, fooSink);
+ registry.addSink(BarEvent.class, barSink);
+
+ assertEquals("incorrect sink count", 2, registry.getSinks().size());
+ assertEquals("incorrect sink", fooSink, registry.getSink(FooEvent.class));
+ assertEquals("incorrect sink", barSink, registry.getSink(BarEvent.class));
+
+ registry.removeSink(FooEvent.class);
+ assertNull("incorrect sink", registry.getSink(FooEvent.class));
+ assertEquals("incorrect sink", barSink, registry.getSink(BarEvent.class));
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/EventDeliveryServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/EventDeliveryServiceAdapter.java
new file mode 100644
index 00000000..6d5e8934
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/EventDeliveryServiceAdapter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.event;
+
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * Testing adapter for the event delivery service.
+ */
+public class EventDeliveryServiceAdapter implements EventDeliveryService {
+ @Override
+ public void setDispatchTimeLimit(long millis) {
+
+ }
+
+ @Override
+ public long getDispatchTimeLimit() {
+ return 0;
+ }
+
+ @Override
+ public void post(Event event) {
+
+ }
+
+ @Override
+ public <E extends Event> void addSink(Class<E> eventClass, EventSink<E> sink) {
+
+ }
+
+ @Override
+ public <E extends Event> void removeSink(Class<E> eventClass) {
+
+ }
+
+ @Override
+ public <E extends Event> EventSink<E> getSink(Class<E> eventClass) {
+ return null;
+ }
+
+ @Override
+ public Set<Class<? extends Event>> getSinks() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/ListenerRegistryTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/ListenerRegistryTest.java
new file mode 100644
index 00000000..8cce6417
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/ListenerRegistryTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link ListenerRegistry}.
+ */
+public class ListenerRegistryTest {
+
+ private static final TestEvent FOO_EVENT =
+ new TestEvent(TestEvent.Type.FOO, "foo");
+ private static final TestEvent BAR_EVENT =
+ new TestEvent(TestEvent.Type.BAR, "bar");
+
+ private TestListener listener;
+ private TestListener secondListener;
+ private TestListenerRegistry manager;
+
+ @Before
+ public void setUp() {
+ listener = new TestListener();
+ secondListener = new TestListener();
+ manager = new TestListenerRegistry();
+ }
+
+ @Test
+ public void basics() {
+ manager.addListener(listener);
+ manager.addListener(secondListener);
+
+ manager.process(BAR_EVENT);
+ assertTrue("BAR not processed", listener.events.contains(BAR_EVENT));
+ assertTrue("BAR not processed", secondListener.events.contains(BAR_EVENT));
+
+ manager.removeListener(listener);
+
+ manager.process(FOO_EVENT);
+ assertFalse("FOO processed", listener.events.contains(FOO_EVENT));
+ assertTrue("FOO not processed", secondListener.events.contains(FOO_EVENT));
+ }
+
+ @Test
+ public void badListener() {
+ listener = new BrokenListener();
+
+ manager.addListener(listener);
+ manager.addListener(secondListener);
+
+ manager.process(BAR_EVENT);
+ assertFalse("BAR processed", listener.events.contains(BAR_EVENT));
+ assertFalse("error not reported", manager.errors.isEmpty());
+ assertTrue("BAR not processed", secondListener.events.contains(BAR_EVENT));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestEvent.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestEvent.java
new file mode 100644
index 00000000..8a507151
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Test event fixture.
+ */
+public class TestEvent extends AbstractEvent<TestEvent.Type, String> {
+
+ public enum Type { FOO, BAR }
+
+ public TestEvent(Type type, String subject) {
+ super(type, subject);
+ }
+
+ public TestEvent(Type type, String subject, long timestamp) {
+ super(type, subject, timestamp);
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListener.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListener.java
new file mode 100644
index 00000000..213f8c59
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test event listener fixture.
+ */
+public class TestListener implements EventListener<TestEvent> {
+
+ public final List<TestEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(TestEvent event) {
+ events.add(event);
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListenerRegistry.java b/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListenerRegistry.java
new file mode 100644
index 00000000..fd9b6d08
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/event/TestListenerRegistry.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test event listener manager fixture.
+ */
+public class TestListenerRegistry
+ extends ListenerRegistry<TestEvent, TestListener> {
+
+ public final List<Throwable> errors = new ArrayList<>();
+
+ @Override
+ protected void reportProblem(TestEvent event, Throwable error) {
+ super.reportProblem(event, error);
+ errors.add(error);
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipServiceAdapter.java
new file mode 100644
index 00000000..7db9b38b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipServiceAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.mastership;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Test adapter for mastership service.
+ */
+public class MastershipServiceAdapter implements MastershipService {
+ @Override
+ public MastershipRole getLocalRole(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+ return null;
+ }
+
+ @Override
+ public void addListener(MastershipListener listener) {
+ }
+
+ @Override
+ public void removeListener(MastershipListener listener) {
+ }
+
+ @Override
+ public RoleInfo getNodesFor(DeviceId deviceId) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipTermTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipTermTest.java
new file mode 100644
index 00000000..0ca9436b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/mastership/MastershipTermTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.mastership;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+import org.junit.Test;
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.testing.EqualsTester;
+
+public class MastershipTermTest {
+
+ private static final NodeId N1 = new NodeId("foo");
+ private static final NodeId N2 = new NodeId("bar");
+
+ private static final MastershipTerm TERM1 = MastershipTerm.of(N1, 0);
+ private static final MastershipTerm TERM2 = MastershipTerm.of(N2, 1);
+ private static final MastershipTerm TERM3 = MastershipTerm.of(N2, 1);
+ private static final MastershipTerm TERM4 = MastershipTerm.of(N1, 1);
+
+ @Test
+ public void basics() {
+ assertEquals("incorrect term number", 0, TERM1.termNumber());
+ assertEquals("incorrect master", new NodeId("foo"), TERM1.master());
+ }
+
+ @Test
+ public void testEquality() {
+ new EqualsTester().addEqualityGroup(MastershipTerm.of(N1, 0), TERM1)
+ .addEqualityGroup(TERM2, TERM3)
+ .addEqualityGroup(TERM4)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the MembershipTerm class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(MastershipTerm.class);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
new file mode 100644
index 00000000..2aecabd4
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the connection point entity.
+ */
+public class ConnectPointTest {
+
+ private static final DeviceId DID1 = deviceId("1");
+ private static final DeviceId DID2 = deviceId("2");
+ private static final PortNumber P1 = portNumber(1);
+ private static final PortNumber P2 = portNumber(2);
+
+ @Test
+ public void basics() {
+ ConnectPoint p = new ConnectPoint(DID1, P2);
+ assertEquals("incorrect element id", DID1, p.deviceId());
+ assertEquals("incorrect element id", P2, p.port());
+ }
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(new ConnectPoint(DID1, P1), new ConnectPoint(DID1, P1))
+ .addEqualityGroup(new ConnectPoint(DID1, P2), new ConnectPoint(DID1, P2))
+ .addEqualityGroup(new ConnectPoint(DID2, P1), new ConnectPoint(DID2, P1))
+ .testEquals();
+ }
+
+ @Test
+ public void testParseDeviceConnectPoint() {
+ String cp = "of:0011223344556677/1";
+
+ ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(cp);
+ assertEquals("of:0011223344556677", connectPoint.deviceId().toString());
+ assertEquals("1", connectPoint.port().toString());
+
+ expectDeviceParseException("");
+ expectDeviceParseException("1/");
+ expectDeviceParseException("1/1/1");
+ expectDeviceParseException("of:0011223344556677/word");
+ }
+
+ /**
+ * Parse a device connect point and expect an exception to be thrown.
+ *
+ * @param string string to parse
+ */
+ private static void expectDeviceParseException(String string) {
+ try {
+ ConnectPoint.deviceConnectPoint(string);
+ fail("Expected exception was not thrown");
+ } catch (Exception e) {
+ assertTrue(true);
+ }
+ }
+
+ @Test
+ public void testParseHostConnectPoint() {
+ String cp = "16:3A:BD:6E:31:E4/-1/1";
+
+ ConnectPoint connectPoint = ConnectPoint.hostConnectPoint(cp);
+ assertEquals("16:3A:BD:6E:31:E4/-1", connectPoint.hostId().toString());
+ assertEquals("1", connectPoint.port().toString());
+
+ expectHostParseException("");
+ expectHostParseException("1/");
+ expectHostParseException("1/1");
+ expectHostParseException("1/1/1/1");
+ expectHostParseException("16:3A:BD:6E:31:E4/word/1");
+ expectHostParseException("16:3A:BD:6E:31:E4/1/word");
+ }
+
+ /**
+ * Parse a host connect point and expect an exception to be thrown.
+ *
+ * @param string string to parse
+ */
+ private static void expectHostParseException(String string) {
+ try {
+ ConnectPoint.hostConnectPoint(string);
+ fail("Expected exception was not thrown");
+ } catch (Exception e) {
+ assertTrue(true);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultAnnotationsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultAnnotationsTest.java
new file mode 100644
index 00000000..1bac285d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultAnnotationsTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.junit.Assert.*;
+import static org.onosproject.net.DefaultAnnotations.builder;
+
+/**
+ * Tests of the default annotations.
+ */
+public class DefaultAnnotationsTest {
+
+ private DefaultAnnotations annotations;
+
+ @Test
+ public void basics() {
+ annotations = builder().set("foo", "1").set("bar", "2").build();
+ assertEquals("incorrect keys", of("foo", "bar"), annotations.keys());
+ assertEquals("incorrect value", "1", annotations.value("foo"));
+ assertEquals("incorrect value", "2", annotations.value("bar"));
+ }
+
+ @Test
+ public void empty() {
+ annotations = builder().build();
+ assertTrue("incorrect keys", annotations.keys().isEmpty());
+ }
+
+ @Test
+ public void remove() {
+ annotations = builder().remove("foo").set("bar", "2").build();
+ assertEquals("incorrect keys", of("foo", "bar"), annotations.keys());
+ assertNull("incorrect value", annotations.value("foo"));
+ assertEquals("incorrect value", "2", annotations.value("bar"));
+ }
+
+ @Test
+ public void union() {
+ annotations = builder().set("foo", "1").set("bar", "2").remove("buz").build();
+ assertEquals("incorrect keys", of("foo", "bar", "buz"), annotations.keys());
+
+ SparseAnnotations updates = builder().remove("foo").set("bar", "3").set("goo", "4").remove("fuzz").build();
+
+ SparseAnnotations result = DefaultAnnotations.union(annotations, updates);
+
+ assertTrue("remove instruction in original remains", result.isRemoved("buz"));
+ assertTrue("remove instruction in update remains", result.isRemoved("fuzz"));
+ assertEquals("incorrect keys", of("buz", "goo", "bar", "fuzz"), result.keys());
+ assertNull("incorrect value", result.value("foo"));
+ assertEquals("incorrect value", "3", result.value("bar"));
+ assertEquals("incorrect value", "4", result.value("goo"));
+ }
+
+ @Test
+ public void merge() {
+ annotations = builder().set("foo", "1").set("bar", "2").build();
+ assertEquals("incorrect keys", of("foo", "bar"), annotations.keys());
+
+ SparseAnnotations updates = builder().remove("foo").set("bar", "3").set("goo", "4").build();
+
+ annotations = DefaultAnnotations.merge(annotations, updates);
+ assertEquals("incorrect keys", of("goo", "bar"), annotations.keys());
+ assertNull("incorrect value", annotations.value("foo"));
+ assertEquals("incorrect value", "3", annotations.value("bar"));
+ }
+
+ @Test
+ public void noopMerge() {
+ annotations = builder().set("foo", "1").set("bar", "2").build();
+ assertEquals("incorrect keys", of("foo", "bar"), annotations.keys());
+
+ SparseAnnotations updates = builder().build();
+ assertSame("same annotations expected", annotations,
+ DefaultAnnotations.merge(annotations, updates));
+ assertSame("same annotations expected", annotations,
+ DefaultAnnotations.merge(annotations, null));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void badMerge() {
+ DefaultAnnotations.merge(null, null);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultDeviceTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultDeviceTest.java
new file mode 100644
index 00000000..1b0319a5
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultDeviceTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.DeviceId.deviceId;
+
+/**
+ * Test of the default device model entity.
+ */
+public class DefaultDeviceTest {
+
+ static final ProviderId PID = new ProviderId("of", "foo");
+ static final DeviceId DID1 = deviceId("of:foo");
+ static final DeviceId DID2 = deviceId("of:bar");
+ static final String MFR = "whitebox";
+ static final String HW = "1.1.x";
+ static final String SW = "3.9.1";
+ static final String SN1 = "43311-12345";
+ static final String SN2 = "42346-43512";
+ static final ChassisId CID = new ChassisId();
+
+ @Test
+ public void testEquality() {
+ Device d1 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
+ Device d2 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
+ Device d3 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2, CID);
+ Device d4 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2, CID);
+ Device d5 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN1, CID);
+
+ new EqualsTester().addEqualityGroup(d1, d2)
+ .addEqualityGroup(d3, d4)
+ .addEqualityGroup(d5)
+ .testEquals();
+ }
+
+ @Test
+ public void basics() {
+ Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
+ validate(device);
+ }
+
+ @Test
+ public void annotations() {
+ Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID,
+ DefaultAnnotations.builder().set("foo", "bar").build());
+ validate(device);
+ assertEquals("incorrect provider", "bar", device.annotations().value("foo"));
+ }
+
+ private void validate(Device device) {
+ assertEquals("incorrect provider", PID, device.providerId());
+ assertEquals("incorrect id", DID1, device.id());
+ assertEquals("incorrect type", SWITCH, device.type());
+ assertEquals("incorrect manufacturer", MFR, device.manufacturer());
+ assertEquals("incorrect hw", HW, device.hwVersion());
+ assertEquals("incorrect sw", SW, device.swVersion());
+ assertEquals("incorrect serial", SN1, device.serialNumber());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultEdgeLinkTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultEdgeLinkTest.java
new file mode 100644
index 00000000..ee5b0e32
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultEdgeLinkTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+import static org.onosproject.net.DefaultLinkTest.cp;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.HostId.hostId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the default edge link model entity.
+ */
+public class DefaultEdgeLinkTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final HostId HID1 = hostId("00:00:00:00:00:01/-1");
+ private static final HostId HID2 = hostId("00:00:00:00:00:01/-1");
+ private static final PortNumber P0 = portNumber(0);
+ private static final PortNumber P1 = portNumber(1);
+
+ @Test
+ public void testEquality() {
+ EdgeLink l1 = new DefaultEdgeLink(PID, cp(HID1, P0),
+ new HostLocation(DID1, P1, 123L), true);
+ EdgeLink l2 = new DefaultEdgeLink(PID, cp(HID1, P0),
+ new HostLocation(DID1, P1, 123L), true);
+
+ EdgeLink l3 = new DefaultEdgeLink(PID, cp(HID2, P0),
+ new HostLocation(DID1, P1, 123L), false);
+ EdgeLink l4 = new DefaultEdgeLink(PID, cp(HID2, P0),
+ new HostLocation(DID1, P1, 123L), false);
+
+ new EqualsTester().addEqualityGroup(l1, l2)
+ .addEqualityGroup(l3, l4)
+ .testEquals();
+ }
+
+ @Test
+ public void basics() {
+ HostLocation hostLocation = new HostLocation(DID1, P1, 123L);
+ EdgeLink link = new DefaultEdgeLink(PID, cp(HID1, P0), hostLocation, false);
+ assertEquals("incorrect src", cp(HID1, P0), link.dst());
+ assertEquals("incorrect dst", hostLocation, link.src());
+ assertEquals("incorrect type", Link.Type.EDGE, link.type());
+ assertEquals("incorrect hostId", HID1, link.hostId());
+ assertEquals("incorrect connect point", hostLocation, link.hostLocation());
+ assertEquals("incorrect time", 123L, link.hostLocation().time());
+ }
+
+ @Test
+ public void phantomIngress() {
+ HostLocation hostLocation = new HostLocation(DID1, P1, 123L);
+ EdgeLink link = createEdgeLink(hostLocation, true);
+ assertEquals("incorrect dst", hostLocation, link.dst());
+ assertEquals("incorrect type", Link.Type.EDGE, link.type());
+ assertEquals("incorrect connect point", hostLocation, link.hostLocation());
+ assertEquals("incorrect time", 123L, link.hostLocation().time());
+ }
+
+ @Test
+ public void phantomEgress() {
+ ConnectPoint hostLocation = new ConnectPoint(DID1, P1);
+ EdgeLink link = createEdgeLink(hostLocation, false);
+ assertEquals("incorrect src", hostLocation, link.src());
+ assertEquals("incorrect type", Link.Type.EDGE, link.type());
+ assertEquals("incorrect connect point", hostLocation, link.hostLocation());
+ assertEquals("incorrect time", 0L, link.hostLocation().time());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultHostTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultHostTest.java
new file mode 100644
index 00000000..1fb6da5a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultHostTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+public class DefaultHostTest extends TestDeviceParams {
+
+ @Test
+ public void testEquality() {
+ Host h1 = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1, IPSET1);
+ Host h2 = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1, IPSET1);
+ Host h3 = new DefaultHost(PID, HID2, MAC2, VLAN2, LOC2, IPSET2);
+ Host h4 = new DefaultHost(PID, HID2, MAC2, VLAN2, LOC2, IPSET2);
+ Host h5 = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC2, IPSET1);
+
+ new EqualsTester().addEqualityGroup(h1, h2)
+ .addEqualityGroup(h3, h4)
+ .addEqualityGroup(h5)
+ .testEquals();
+ }
+
+ @Test
+ public void basics() {
+ Host host = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1, IPSET1);
+ assertEquals("incorrect provider", PID, host.providerId());
+ assertEquals("incorrect id", HID1, host.id());
+ assertEquals("incorrect type", MAC1, host.mac());
+ assertEquals("incorrect VLAN", VLAN1, host.vlan());
+ assertEquals("incorrect location", LOC1, host.location());
+ assertEquals("incorrect IP's", IPSET1, host.ipAddresses());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultLinkTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultLinkTest.java
new file mode 100644
index 00000000..bd5821bb
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultLinkTest.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.net;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+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.PortNumber.portNumber;
+
+/**
+ * Test of the default link model entity.
+ */
+public class DefaultLinkTest {
+
+ 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 PortNumber P1 = portNumber(1);
+ private static final PortNumber P2 = portNumber(2);
+
+ public static ConnectPoint cp(ElementId id, PortNumber pn) {
+ return new ConnectPoint(id, pn);
+ }
+
+ @Test
+ public void testEquality() {
+ Link l1 = new DefaultLink(PID, cp(DID1, P1), cp(DID2, P2), DIRECT);
+ Link l2 = new DefaultLink(PID, cp(DID1, P1), cp(DID2, P2), DIRECT);
+ Link l3 = new DefaultLink(PID, cp(DID1, P2), cp(DID2, P2), DIRECT);
+ Link l4 = new DefaultLink(PID, cp(DID1, P2), cp(DID2, P2), DIRECT);
+ Link l5 = new DefaultLink(PID, cp(DID1, P2), cp(DID2, P2), INDIRECT);
+
+ new EqualsTester().addEqualityGroup(l1, l2)
+ .addEqualityGroup(l3, l4)
+ .addEqualityGroup(l5)
+ .testEquals();
+ }
+
+ @Test
+ public void basics() {
+ Link link = new DefaultLink(PID, cp(DID1, P1), cp(DID2, P2), DIRECT);
+ assertEquals("incorrect src", cp(DID1, P1), link.src());
+ assertEquals("incorrect dst", cp(DID2, P2), link.dst());
+ assertEquals("incorrect type", DIRECT, link.type());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultPortTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultPortTest.java
new file mode 100644
index 00000000..592115d7
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DefaultPortTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Port.Type.COPPER;
+import static org.onosproject.net.Port.Type.FIBER;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the default port model entity.
+ */
+public class DefaultPortTest {
+
+ 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 PortNumber P1 = portNumber(1);
+ private static final PortNumber P2 = portNumber(2);
+ private static final long SP1 = 1_000_000;
+
+ @Test
+ public void testEquality() {
+ Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
+ new ChassisId());
+ Port p1 = new DefaultPort(device, portNumber(1), true, COPPER, SP1);
+ Port p2 = new DefaultPort(device, portNumber(1), true, COPPER, SP1);
+ Port p3 = new DefaultPort(device, portNumber(2), true, FIBER, SP1);
+ Port p4 = new DefaultPort(device, portNumber(2), true, FIBER, SP1);
+ Port p5 = new DefaultPort(device, portNumber(1), false);
+
+ new EqualsTester().addEqualityGroup(p1, p2)
+ .addEqualityGroup(p3, p4)
+ .addEqualityGroup(p5)
+ .testEquals();
+ }
+
+ @Test
+ public void basics() {
+ Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
+ new ChassisId());
+ Port port = new DefaultPort(device, portNumber(1), true, FIBER, SP1);
+ assertEquals("incorrect element", device, port.element());
+ assertEquals("incorrect number", portNumber(1), port.number());
+ assertEquals("incorrect state", true, port.isEnabled());
+ assertEquals("incorrect speed", SP1, port.portSpeed());
+ assertEquals("incorrect type", FIBER, port.type());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/DeviceIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DeviceIdTest.java
new file mode 100644
index 00000000..ab5373f8
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/DeviceIdTest.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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.onosproject.net.DeviceId.deviceId;
+
+/**
+ * Test of the device identifier.
+ */
+public class DeviceIdTest {
+
+ @Test
+ public void basics() {
+ new EqualsTester()
+ .addEqualityGroup(deviceId("of:foo"),
+ deviceId("of:foo"))
+ .addEqualityGroup(deviceId("of:bar"))
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/HostIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/HostIdTest.java
new file mode 100644
index 00000000..43425e21
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/HostIdTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import static org.onosproject.net.HostId.hostId;
+
+/**
+ * Test for the host identifier.
+ */
+public class HostIdTest {
+
+ private static final MacAddress MAC1 = MacAddress.valueOf("00:11:00:00:00:01");
+ private static final MacAddress MAC2 = MacAddress.valueOf("00:22:00:00:00:02");
+ private static final VlanId VLAN1 = VlanId.vlanId((short) 11);
+ private static final VlanId VLAN2 = VlanId.vlanId((short) 22);
+
+ @Test
+ public void basics() {
+ new EqualsTester()
+ .addEqualityGroup(hostId(MAC1, VLAN1), hostId(MAC1, VLAN1))
+ .addEqualityGroup(hostId(MAC2, VLAN2), hostId(MAC2, VLAN2))
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/IndexedLambdaTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/IndexedLambdaTest.java
new file mode 100644
index 00000000..8cbe8232
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/IndexedLambdaTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+/**
+ * Test for IndexedLambda.
+ */
+public class IndexedLambdaTest {
+ /**
+ * Tests equality of IndexedLambda instances.
+ */
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(Lambda.indexedLambda(10), Lambda.indexedLambda(10))
+ .addEqualityGroup(Lambda.indexedLambda(11), Lambda.indexedLambda(11), Lambda.indexedLambda(11))
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/LinkKeyTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/LinkKeyTest.java
new file mode 100644
index 00000000..4dfc1399
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/LinkKeyTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Unit tests for the LinkKey class.
+ */
+public class LinkKeyTest {
+
+ static final DeviceId D1 = deviceId("1");
+ static final DeviceId D2 = deviceId("2");
+ static final PortNumber P1 = portNumber(1);
+ static final PortNumber P2 = portNumber(2);
+
+ static final ConnectPoint SRC1 = new ConnectPoint(D1, P1);
+ static final ConnectPoint DST1 = new ConnectPoint(D2, P1);
+ static final ConnectPoint DST2 = new ConnectPoint(D2, P2);
+
+
+ /**
+ * Checks that the LinkKey class is immutable.
+ */
+ @Test
+ public void testLinkKeyImmutability() {
+ assertThatClassIsImmutable(LinkKey.class);
+ }
+
+ /**
+ * Check null source connection.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testNullSrc() {
+ LinkKey key = LinkKey.linkKey(null, DST1);
+ }
+
+ /**
+ * Check null destination connection.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testNullDst() {
+ LinkKey key = LinkKey.linkKey(SRC1, null);
+ }
+
+ /**
+ * Check that two LinkKeys based on the same source/destination pair compare
+ * equal.
+ */
+ @Test
+ public void testCompareEquals() {
+ LinkKey k1 = LinkKey.linkKey(SRC1, DST2);
+ LinkKey k2 = LinkKey.linkKey(SRC1, DST2);
+
+ assertThat(k1, is(equalTo(k2)));
+ }
+
+ /**
+ * Check that two LinkKeys based on different source/destination pairs compare
+ * not equal.
+ */
+ @Test
+ public void testCompareNotEquals() {
+ LinkKey k1 = LinkKey.linkKey(SRC1, DST1);
+ LinkKey k2 = LinkKey.linkKey(SRC1, DST2);
+
+ assertThat(k1, is(not(equalTo(k2))));
+ assertThat(k1, is(not(equalTo(new Object()))));
+ }
+
+ /**
+ * Check that two LinkKeys based on the same source/destination pair compare
+ * equal.
+ */
+ @Test
+ public void testHashCodeEquals() {
+ LinkKey k1 = LinkKey.linkKey(SRC1, DST2);
+ LinkKey k2 = LinkKey.linkKey(SRC1, DST2);
+
+ assertThat(k1.hashCode(), is(equalTo(k2.hashCode())));
+ }
+
+ /**
+ * Check that two LinkKeys based on different source/destination pairs compare
+ * not equal.
+ */
+ @Test
+ public void testHashCodeNotEquals() {
+ LinkKey k1 = LinkKey.linkKey(SRC1, DST1);
+ LinkKey k2 = LinkKey.linkKey(SRC1, DST2);
+
+ assertThat(k1.hashCode(), is(not(equalTo(k2.hashCode()))));
+ }
+
+ /**
+ * Check the toString() method of LinkKey.
+ */
+ @Test
+ public void testToString() {
+ LinkKey k1 = LinkKey.linkKey(SRC1, DST1);
+ String k1String = k1.toString();
+ assertThat(k1String, allOf(containsString("LinkKey{"),
+ containsString("src=ConnectPoint{elementId=1, portNumber=1}"),
+ containsString("dst=ConnectPoint{elementId=2, portNumber=1}")));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/NetTestTools.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/NetTestTools.java
new file mode 100644
index 00000000..176fe40c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/NetTestTools.java
@@ -0,0 +1,138 @@
+/*
+ * 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;
+
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.ChassisId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.net.provider.ProviderId;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.packet.MacAddress.valueOf;
+import static org.onlab.packet.VlanId.vlanId;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.HostId.hostId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Miscellaneous tools for testing core related to the network model.
+ */
+public final class NetTestTools {
+
+ private NetTestTools() {
+ }
+
+ public static final ProviderId PID = new ProviderId("of", "foo");
+ public static final ApplicationId APP_ID = new TestApplicationId("foo");
+
+ // Short-hand for producing a device id from a string
+ public static DeviceId did(String id) {
+ return deviceId("of:" + id);
+ }
+
+
+ // Short-hand for producing a host id from a string
+ public static HostId hid(String id) {
+ return hostId(id);
+ }
+
+ // Crates a new device with the specified id
+ public static Device device(String id) {
+ return new DefaultDevice(PID, did(id), Device.Type.SWITCH,
+ "mfg", "1.0", "1.1", "1234", new ChassisId());
+ }
+
+ // Crates a new host with the specified id
+ public static Host host(String id, String did) {
+ return new DefaultHost(PID, hid(id), valueOf(1234), vlanId((short) 2),
+ new HostLocation(did(did), portNumber(1), 321),
+ new HashSet<>());
+ }
+
+ // Short-hand for creating a connection point.
+ public static ConnectPoint connectPoint(String id, int port) {
+ return new ConnectPoint(did(id), portNumber(port));
+ }
+
+ // Short-hand for creating a link.
+ public static Link link(String src, int sp, String dst, int dp) {
+ return new DefaultLink(PID,
+ connectPoint(src, sp),
+ connectPoint(dst, dp),
+ Link.Type.DIRECT);
+ }
+
+ // Creates a path that leads through the given devices.
+ public static Path createPath(String... ids) {
+ List<Link> links = new ArrayList<>();
+ for (int i = 0; i < ids.length - 1; i++) {
+ links.add(link(ids[i], i, ids[i + 1], i));
+ }
+ return new DefaultPath(PID, links, ids.length);
+ }
+
+ // Creates OCh signal
+ public static OchSignal createLambda() {
+ return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_6P25GHZ, 8, 4);
+ }
+
+ /**
+ * Verifies that Annotations created by merging {@code annotations} is
+ * equal to actual Annotations.
+ *
+ * @param actual annotations to check
+ * @param annotations expected annotations
+ */
+ public static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+ DefaultAnnotations expected = DefaultAnnotations.builder().build();
+ for (SparseAnnotations a : annotations) {
+ expected = DefaultAnnotations.merge(expected, a);
+ }
+ assertEquals(expected.keys(), actual.keys());
+ for (String key : expected.keys()) {
+ assertEquals(expected.value(key), actual.value(key));
+ }
+ }
+
+ /**
+ * Injects the given event delivery service into the specified manager
+ * component.
+ *
+ * @param manager manager component
+ * @param svc service reference to be injected
+ */
+ public static void injectEventDispatcher(Object manager, EventDeliveryService svc) {
+ Class mc = manager.getClass();
+ for (Field f : mc.getSuperclass().getDeclaredFields()) {
+ if (f.getType().equals(EventDeliveryService.class)) {
+ try {
+ TestUtils.setField(manager, f.getName(), svc);
+ } catch (TestUtils.TestUtilsException e) {
+ throw new IllegalArgumentException("Unable to inject reference", e);
+ }
+ break;
+ }
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/OchSignalTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/OchSignalTest.java
new file mode 100644
index 00000000..c171d523
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/OchSignalTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test for OchSignal.
+ */
+public class OchSignalTest {
+
+ private final Lambda och1 = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 1, 1);
+ private final Lambda sameOch1 = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 1, 1);
+ private final Lambda och2 = Lambda.ochSignal(GridType.CWDM, ChannelSpacing.CHL_6P25GHZ, 4, 1);
+ private final Lambda sameOch2 = Lambda.ochSignal(GridType.CWDM, ChannelSpacing.CHL_6P25GHZ, 4, 1);
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(och1, sameOch1)
+ .addEqualityGroup(och2, sameOch2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/PortNumberTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/PortNumberTest.java
new file mode 100644
index 00000000..6f7b2c2d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/PortNumberTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the port number.
+ */
+public class PortNumberTest {
+
+ @Test
+ public void basics() {
+ new EqualsTester()
+ .addEqualityGroup(portNumber(123), portNumber("123"))
+ .addEqualityGroup(portNumber(321), portNumber(321, "LIM-3-2-1"))
+ .testEquals();
+ }
+
+ @Test
+ public void number() {
+ assertEquals("incorrect long value", 12345, portNumber(12345).toLong());
+ }
+
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/TestDeviceParams.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/TestDeviceParams.java
new file mode 100644
index 00000000..2d8aae3c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/TestDeviceParams.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import static org.onosproject.net.DeviceId.deviceId;
+
+import java.util.Set;
+
+import org.onosproject.net.provider.ProviderId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Provides a set of test DefaultDevice parameters for use with Host-
+ * related tests.
+ */
+public abstract class TestDeviceParams {
+
+ protected static final ProviderId PID = new ProviderId("of", "foo");
+ protected static final DeviceId DID1 = deviceId("of:foo");
+ protected static final DeviceId DID2 = deviceId("of:bar");
+ protected static final MacAddress MAC1 = MacAddress.valueOf("00:11:00:00:00:01");
+ protected static final MacAddress MAC2 = MacAddress.valueOf("00:22:00:00:00:02");
+ protected static final VlanId VLAN1 = VlanId.vlanId((short) 11);
+ protected static final VlanId VLAN2 = VlanId.vlanId((short) 22);
+ protected static final IpAddress IP1 = IpAddress.valueOf("10.0.0.1");
+ protected static final IpAddress IP2 = IpAddress.valueOf("10.0.0.2");
+ protected static final IpAddress IP3 = IpAddress.valueOf("10.0.0.3");
+
+ protected static final PortNumber P1 = PortNumber.portNumber(100);
+ protected static final PortNumber P2 = PortNumber.portNumber(200);
+ protected static final HostId HID1 = HostId.hostId(MAC1, VLAN1);
+ protected static final HostId HID2 = HostId.hostId(MAC2, VLAN2);
+ protected static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L);
+ protected static final HostLocation LOC2 = new HostLocation(DID2, P2, 123L);
+ protected static final Set<IpAddress> IPSET1 = Sets.newHashSet(IP1, IP2);
+ protected static final Set<IpAddress> IPSET2 = Sets.newHashSet(IP1, IP3);
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigRegistryAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigRegistryAdapter.java
new file mode 100644
index 00000000..6201c0b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigRegistryAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.config;
+
+import java.util.Set;
+
+/**
+ * Test adapter for network configuration service registry.
+ */
+public class NetworkConfigRegistryAdapter extends NetworkConfigServiceAdapter implements NetworkConfigRegistry {
+
+ public void registerConfigFactory(ConfigFactory configFactory) {
+ }
+
+ public void unregisterConfigFactory(ConfigFactory configFactory) {
+ }
+
+ public Set<ConfigFactory> getConfigFactories() {
+ return null;
+ }
+
+ public <S, C extends Config<S>> Set<ConfigFactory<S, C>> getConfigFactories(Class<S> subjectClass) {
+ return null;
+ }
+
+ public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java
new file mode 100644
index 00000000..b70d14e8
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/config/NetworkConfigServiceAdapter.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.Set;
+
+/**
+ * Test adapter for network configuration service.
+ */
+public class NetworkConfigServiceAdapter implements NetworkConfigService {
+ @Override
+ public Set<Class> getSubjectClasses() {
+ return null;
+ }
+
+ @Override
+ public SubjectFactory getSubjectFactory(String subjectKey) {
+ return null;
+ }
+
+ @Override
+ public SubjectFactory getSubjectFactory(Class subjectClass) {
+ return null;
+ }
+
+ @Override
+ public Class<? extends Config> getConfigClass(String subjectKey, String configKey) {
+ return null;
+ }
+
+ @Override
+ public <S> Set<S> getSubjects(Class<S> subjectClass) {
+ return null;
+ }
+
+ @Override
+ public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
+ return null;
+ }
+
+ @Override
+ public <S> Set<? extends Config<S>> getConfigs(S subject) {
+ return null;
+ }
+
+ @Override
+ public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+ return null;
+ }
+
+ @Override
+ public <S, C extends Config<S>> C addConfig(S subject, Class<C> configClass) {
+ return null;
+ }
+
+ @Override
+ public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
+ return null;
+ }
+
+ @Override
+ public <S, C extends Config<S>> void removeConfig(S subject, Class<C> configClass) {
+
+ }
+
+ @Override
+ public void addListener(NetworkConfigListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(NetworkConfigListener listener) {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultDeviceDescriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultDeviceDescriptionTest.java
new file mode 100644
index 00000000..3dcdc22d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultDeviceDescriptionTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.device;
+
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.Device.Type.SWITCH;
+
+/**
+ * Test of the default device description.
+ */
+public class DefaultDeviceDescriptionTest {
+
+ private static final URI DURI = URI.create("of:foo");
+ 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();
+
+
+ @Test
+ public void basics() {
+ DeviceDescription device =
+ new DefaultDeviceDescription(DURI, SWITCH, MFR, HW, SW, SN, CID);
+ assertEquals("incorrect uri", DURI, device.deviceURI());
+ assertEquals("incorrect type", SWITCH, device.type());
+ assertEquals("incorrect manufacturer", MFR, device.manufacturer());
+ assertEquals("incorrect hw", HW, device.hwVersion());
+ assertEquals("incorrect sw", SW, device.swVersion());
+ assertEquals("incorrect serial", SN, device.serialNumber());
+ assertTrue("incorrect toString", device.toString().contains("uri=of:foo"));
+ assertTrue("Incorrect chassis", device.chassisId().value() == 0);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultPortStatisticsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultPortStatisticsTest.java
new file mode 100644
index 00000000..b691ebc3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DefaultPortStatisticsTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.device;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.onosproject.net.NetTestTools;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * DefaultPortStatistics unit tests.
+ */
+public class DefaultPortStatisticsTest {
+
+ private final PortStatistics stats1 = DefaultPortStatistics.builder()
+ .setBytesReceived(1)
+ .setBytesSent(2)
+ .setDurationNano(3)
+ .setDurationSec(4)
+ .setPacketsReceived(5)
+ .setPacketsSent(6)
+ .setPacketsRxDropped(7)
+ .setPacketsRxErrors(8)
+ .setPacketsTxDropped(9)
+ .setPacketsTxErrors(10)
+ .setPort(80)
+ .setDeviceId(NetTestTools.did("1"))
+ .build();
+
+ private final PortStatistics stats2 = DefaultPortStatistics.builder()
+ .setBytesReceived(1)
+ .setBytesSent(2)
+ .setDurationNano(3)
+ .setDurationSec(4)
+ .setPacketsReceived(5)
+ .setPacketsSent(6)
+ .setPacketsRxDropped(7)
+ .setPacketsRxErrors(8)
+ .setPacketsTxDropped(9)
+ .setPacketsTxErrors(11)
+ .setPort(80)
+ .setDeviceId(NetTestTools.did("1"))
+ .build();
+
+ /**
+ * Checks that the GroupOperation class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(DefaultPortStatistics.class);
+ }
+
+ @Test
+ public void testConstruction() {
+ assertThat(stats1.bytesReceived(), is(1L));
+ assertThat(stats1.bytesSent(), is(2L));
+ assertThat(stats1.durationNano(), is(3L));
+ assertThat(stats1.durationSec(), is(4L));
+ assertThat(stats1.packetsReceived(), is(5L));
+ assertThat(stats1.packetsSent(), is(6L));
+ assertThat(stats1.packetsRxDropped(), is(7L));
+ assertThat(stats1.packetsRxErrors(), is(8L));
+ assertThat(stats1.packetsTxDropped(), is(9L));
+ assertThat(stats1.packetsTxErrors(), is(10L));
+ assertThat(stats1.port(), is(80));
+ }
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(stats1, stats1)
+ .addEqualityGroup(stats2)
+ .testEquals();
+ }
+
+ /**
+ * Tests that the empty argument list constructor for serialization
+ * is present and creates a proper object.
+ */
+ @Test
+ public void testSerializerConstructor() {
+ try {
+ Constructor[] constructors = DefaultPortStatistics.class.getDeclaredConstructors();
+ assertThat(constructors, notNullValue());
+ Arrays.stream(constructors).filter(ctor ->
+ ctor.getParameterTypes().length == 0)
+ .forEach(noParamsCtor -> {
+ try {
+ noParamsCtor.setAccessible(true);
+ DefaultPortStatistics stats =
+ (DefaultPortStatistics) noParamsCtor.newInstance();
+ assertThat(stats, notNullValue());
+ } catch (Exception e) {
+ Assert.fail("Exception instantiating no parameters constructor");
+ }
+ });
+ } catch (Exception e) {
+ Assert.fail("Exception looking up constructors");
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceClockServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceClockServiceAdapter.java
new file mode 100644
index 00000000..5bfdd76b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceClockServiceAdapter.java
@@ -0,0 +1,21 @@
+package org.onosproject.net.device;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Timestamp;
+
+/**
+ * Test adapter for device clock service.
+ */
+public class DeviceClockServiceAdapter implements DeviceClockService {
+
+ @Override
+ public boolean isTimestampAvailable(DeviceId deviceId) {
+ return false;
+ }
+
+ @Override
+ public Timestamp getTimestamp(DeviceId deviceId) {
+ return null;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceEventTest.java
new file mode 100644
index 00000000..a0fb9358
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceEventTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.device;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+
+import org.junit.Test;
+import org.onosproject.event.AbstractEventTest;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
+
+/**
+ * Tests of the device event.
+ */
+public class DeviceEventTest extends AbstractEventTest {
+
+ private Device createDevice() {
+ return new DefaultDevice(new ProviderId("of", "foo"), deviceId("of:foo"),
+ Device.Type.SWITCH, "box", "hw", "sw", "sn", new ChassisId());
+ }
+
+ @Override
+ @Test
+ public void withTime() {
+ Device device = createDevice();
+ Port port = new DefaultPort(device, PortNumber.portNumber(123), true);
+ DeviceEvent event = new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED,
+ device, port, 123L);
+ validateEvent(event, DeviceEvent.Type.DEVICE_ADDED, device, 123L);
+ assertEquals("incorrect port", port, event.port());
+ }
+
+ @Override
+ @Test
+ public void withoutTime() {
+ Device device = createDevice();
+ Port port = new DefaultPort(device, PortNumber.portNumber(123), true);
+ long before = System.currentTimeMillis();
+ DeviceEvent event = new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device, port);
+ long after = System.currentTimeMillis();
+ validateEvent(event, DeviceEvent.Type.DEVICE_ADDED, device, before, after);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java
new file mode 100644
index 00000000..795e4c0a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.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.device;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+
+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 java.util.Collections;
+import java.util.List;
+
+/**
+ * Test adapter for device service.
+ */
+public class DeviceServiceAdapter implements DeviceService {
+ @Override
+ public int getDeviceCount() {
+ return 0;
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return FluentIterable.from(getDevices())
+ .filter(new Predicate<Device>() {
+
+ @Override
+ public boolean apply(Device input) {
+ return isAvailable(input.id());
+ }
+ });
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public MastershipRole getRole(DeviceId deviceId) {
+ return MastershipRole.NONE;
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public List<PortStatistics> getPortDeltaStatistics(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) {
+ }
+
+ @Override
+ public Iterable<Device> getDevices(Type type) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices(Type type) {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverDataTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverDataTest.java
new file mode 100644
index 00000000..e3d69109
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverDataTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.DeviceId.deviceId;
+
+public class DefaultDriverDataTest {
+
+ public static final DeviceId DEVICE_ID = deviceId("of:0011223344556677");
+
+ DefaultDriver ddc;
+ DefaultDriverData data;
+
+ @Before
+ public void setUp() {
+ ddc = new DefaultDriver("foo.bar", null, "Circus", "lux", "1.2a",
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class),
+ ImmutableMap.of("foo", "bar"));
+ data = new DefaultDriverData(ddc, DEVICE_ID);
+ }
+
+ @Test
+ public void basics() {
+ assertSame("incorrect driver", ddc, data.driver());
+ assertEquals("incorrect device id", DEVICE_ID, data.deviceId());
+ assertTrue("incorrect toString", data.toString().contains("foo.bar"));
+ }
+
+ @Test
+ public void behaviour() {
+ TestBehaviour behaviour = data.behaviour(TestBehaviour.class);
+ assertTrue("incorrect behaviour", behaviour instanceof TestBehaviourImpl);
+ }
+
+ @Test
+ public void setAndClearAnnotations() {
+ data.set("croc", "aqua");
+ data.set("roo", "mars");
+ data.set("dingo", "bat");
+ assertEquals("incorrect property", "bat", data.value("dingo"));
+ data.clear("dingo", "roo");
+ assertNull("incorrect property", data.value("dingo"));
+ assertNull("incorrect property", data.value("root"));
+ assertEquals("incorrect property", "aqua", data.value("croc"));
+ assertEquals("incorrect properties", 1, data.keys().size());
+ }
+
+ @Test
+ public void clearAllAnnotations() {
+ data.set("croc", "aqua");
+ data.set("roo", "mars");
+ data.set("dingo", "bat");
+ assertEquals("incorrect property", "bat", data.value("dingo"));
+ data.clear();
+ assertEquals("incorrect properties", 0, data.keys().size());
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverHandlerTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverHandlerTest.java
new file mode 100644
index 00000000..717cda2e
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverHandlerTest.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.driver;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class DefaultDriverHandlerTest {
+
+ DefaultDriver ddc;
+ DefaultDriverData data;
+ DefaultDriverHandler handler;
+
+ @Before
+ public void setUp() {
+ ddc = new DefaultDriver("foo.bar", null, "Circus", "lux", "1.2a",
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class,
+ TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class),
+ ImmutableMap.of("foo", "bar"));
+ data = new DefaultDriverData(ddc, DefaultDriverDataTest.DEVICE_ID);
+ handler = new DefaultDriverHandler(data);
+ }
+
+ @Test
+ public void basics() {
+ assertSame("incorrect data", data, handler.data());
+ assertTrue("incorrect toString", handler.toString().contains("1.2a"));
+ }
+
+ @Test
+ public void behaviour() {
+ TestBehaviourTwo behaviour = handler.behaviour(TestBehaviourTwo.class);
+ assertTrue("incorrect behaviour", behaviour instanceof TestBehaviourTwoImpl);
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverProviderTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverProviderTest.java
new file mode 100644
index 00000000..4568fd92
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverProviderTest.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.net.driver;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.junit.Assert.assertEquals;
+
+public class DefaultDriverProviderTest {
+
+ @Test
+ public void basics() {
+ DefaultDriverProvider ddp = new DefaultDriverProvider();
+ DefaultDriver one = new DefaultDriver("foo.bar", null, "Circus", "lux", "1.2a",
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class),
+ ImmutableMap.of("foo", "bar"));
+ DefaultDriver two = new DefaultDriver("foo.bar", null, "", "", "",
+ ImmutableMap.of(TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class),
+ ImmutableMap.of("goo", "wee"));
+ DefaultDriver three = new DefaultDriver("goo.foo", null, "BigTop", "better", "2.2",
+ ImmutableMap.of(TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class),
+ ImmutableMap.of("goo", "gee"));
+
+ ddp.addDrivers(of(one, two, three));
+
+ Set<Driver> drivers = ddp.getDrivers();
+ assertEquals("incorrect types", 2, drivers.size());
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverTest.java
new file mode 100644
index 00000000..01cc7a16
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/DefaultDriverTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.driver.DefaultDriverDataTest.DEVICE_ID;
+
+public class DefaultDriverTest {
+
+ @Test
+ public void basics() {
+ DefaultDriver ddp = new DefaultDriver("foo.base", null, "Circus", "lux", "1.2a",
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class,
+ TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class),
+ ImmutableMap.of("foo", "bar"));
+
+ DefaultDriver ddc = new DefaultDriver("foo.bar", ddp, "Circus", "lux", "1.2a",
+ ImmutableMap.of(),
+ ImmutableMap.of("foo", "bar"));
+ assertEquals("incorrect name", "foo.bar", ddc.name());
+ assertEquals("incorrect parent", ddp, ddc.parent());
+ assertEquals("incorrect mfr", "Circus", ddc.manufacturer());
+ assertEquals("incorrect hw", "lux", ddc.hwVersion());
+ assertEquals("incorrect sw", "1.2a", ddc.swVersion());
+
+ assertEquals("incorrect behaviour count", 2, ddp.behaviours().size());
+ assertEquals("incorrect behaviour count", 0, ddc.behaviours().size());
+ assertTrue("incorrect behaviour", ddc.hasBehaviour(TestBehaviour.class));
+
+ Behaviour b1 = ddc.createBehaviour(new DefaultDriverData(ddc, DEVICE_ID), TestBehaviour.class);
+ assertTrue("incorrect behaviour class", b1 instanceof TestBehaviourImpl);
+
+ Behaviour b2 = ddc.createBehaviour(new DefaultDriverHandler(new DefaultDriverData(ddc, DEVICE_ID)),
+ TestBehaviourTwo.class);
+ assertTrue("incorrect behaviour class", b2 instanceof TestBehaviourTwoImpl);
+
+ assertEquals("incorrect property count", 1, ddc.properties().size());
+ assertEquals("incorrect key count", 1, ddc.keys().size());
+ assertEquals("incorrect property", "bar", ddc.value("foo"));
+
+ assertTrue("incorrect toString", ddc.toString().contains("lux"));
+ }
+
+ @Test
+ public void merge() {
+ DefaultDriver one = new DefaultDriver("foo.bar", null, "Circus", "lux", "1.2a",
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class),
+ ImmutableMap.of("foo", "bar"));
+ Driver ddc =
+ one.merge(new DefaultDriver("foo.bar", null, "", "", "",
+ ImmutableMap.of(TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class),
+ ImmutableMap.of("goo", "wee")));
+
+ assertEquals("incorrect name", "foo.bar", ddc.name());
+ assertEquals("incorrect mfr", "Circus", ddc.manufacturer());
+ assertEquals("incorrect hw", "lux", ddc.hwVersion());
+ assertEquals("incorrect sw", "1.2a", ddc.swVersion());
+
+ assertEquals("incorrect behaviour count", 2, ddc.behaviours().size());
+ assertTrue("incorrect behaviour", ddc.hasBehaviour(TestBehaviourTwo.class));
+
+ assertEquals("incorrect property count", 2, ddc.properties().size());
+ assertEquals("incorrect key count", 2, ddc.keys().size());
+ assertEquals("incorrect property", "wee", ddc.value("goo"));
+
+ assertTrue("incorrect toString", ddc.toString().contains("Circus"));
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviour.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviour.java
new file mode 100644
index 00000000..632fae15
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviour.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * Test behaviour.
+ */
+public interface TestBehaviour extends Behaviour {
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourImpl.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourImpl.java
new file mode 100644
index 00000000..ec5c66bc
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourImpl.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * Test behaviour.
+ */
+public class TestBehaviourImpl extends AbstractBehaviour implements TestBehaviour {
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourNoConstructorImpl.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourNoConstructorImpl.java
new file mode 100644
index 00000000..20452368
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourNoConstructorImpl.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Bad test behaviour.
+ */
+public final class TestBehaviourNoConstructorImpl
+ extends AbstractBehaviour implements TestBehaviour {
+ private TestBehaviourNoConstructorImpl() {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwo.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwo.java
new file mode 100644
index 00000000..3399f00c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwo.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * Test behaviour.
+ */
+public interface TestBehaviourTwo extends HandlerBehaviour {
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwoImpl.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwoImpl.java
new file mode 100644
index 00000000..746bcc87
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/TestBehaviourTwoImpl.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * Test behaviour.
+ */
+public class TestBehaviourTwoImpl extends AbstractHandlerBehaviour implements TestBehaviourTwo {
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/XmlDriverLoaderTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/XmlDriverLoaderTest.java
new file mode 100644
index 00000000..f54e7411
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/driver/XmlDriverLoaderTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.driver.DefaultDriverDataTest.DEVICE_ID;
+
+/**
+ * Tests of the XML driver loader implementation.
+ */
+public class XmlDriverLoaderTest {
+
+ @Test
+ public void basics() throws IOException {
+ XmlDriverLoader loader = new XmlDriverLoader(getClass().getClassLoader());
+ InputStream stream = getClass().getResourceAsStream("drivers.1.xml");
+ DriverProvider provider = loader.loadDrivers(stream, null);
+ System.out.println(provider);
+ assertEquals("incorrect driver count", 2, provider.getDrivers().size());
+
+ Iterator<Driver> iterator = provider.getDrivers().iterator();
+ Driver driver = iterator.next();
+ if (!driver.name().equals("foo.1")) {
+ driver = iterator.next();
+ }
+
+ assertEquals("incorrect driver name", "foo.1", driver.name());
+ assertEquals("incorrect driver mfg", "Circus", driver.manufacturer());
+ assertEquals("incorrect driver hw", "1.2a", driver.hwVersion());
+ assertEquals("incorrect driver sw", "2.2", driver.swVersion());
+
+ assertEquals("incorrect driver behaviours", 1, driver.behaviours().size());
+ assertTrue("incorrect driver behaviour", driver.hasBehaviour(TestBehaviour.class));
+
+ assertEquals("incorrect driver properties", 2, driver.properties().size());
+ assertTrue("incorrect driver property", driver.properties().containsKey("p1"));
+ }
+
+ @Test(expected = IOException.class)
+ public void badXML() throws IOException {
+ XmlDriverLoader loader = new XmlDriverLoader(getClass().getClassLoader());
+ loader.loadDrivers(getClass().getResourceAsStream("drivers.bad.xml"), null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void noClass() throws IOException {
+ XmlDriverLoader loader = new XmlDriverLoader(getClass().getClassLoader());
+ loader.loadDrivers(getClass().getResourceAsStream("drivers.noclass.xml"), null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void noConstructor() throws IOException {
+ XmlDriverLoader loader = new XmlDriverLoader(getClass().getClassLoader());
+ InputStream stream = getClass().getResourceAsStream("drivers.noconstructor.xml");
+ DriverProvider provider = loader.loadDrivers(stream, null);
+ Driver driver = provider.getDrivers().iterator().next();
+ driver.createBehaviour(new DefaultDriverData(driver, DEVICE_ID), TestBehaviour.class);
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/BatchOperationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/BatchOperationTest.java
new file mode 100644
index 00000000..9e142e59
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/BatchOperationTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+
+/**
+ * Unit tests for the BatchOperationTest object.
+ */
+public class BatchOperationTest {
+
+ private enum TestType {
+ OP1,
+ OP2,
+ OP3
+ }
+
+ final TestEntry entry1 = new TestEntry(TestType.OP1, new TestTarget(1));
+ final TestEntry entry2 = new TestEntry(TestType.OP2, new TestTarget(2));
+
+
+ private static final class TestTarget {
+ private int id;
+
+ private TestTarget(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return id;
+ }
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+ TestTarget that = (TestTarget) o;
+ return this.id == that.id;
+ }
+
+ }
+
+ private static final class TestEntry extends BatchOperationEntry<TestType, TestTarget> {
+ public TestEntry(TestType operator, TestTarget target) {
+ super(operator, target);
+ }
+ }
+
+ private static final class TestOperation extends BatchOperation<TestEntry> {
+ private TestOperation() {
+ super();
+ }
+
+ private TestOperation(Collection<TestEntry> batchOperations) {
+ super(batchOperations);
+ }
+ }
+
+ /**
+ * Checks that the DefaultFlowRule class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutableBaseClass(BatchOperation.class);
+ }
+
+ /**
+ * Tests the equals(), hashCode() and toString() operations.
+ */
+ @Test
+ public void testEquals() {
+ final List<TestEntry> ops1 = new LinkedList<>();
+ ops1.add(entry1);
+ final List<TestEntry> ops2 = new LinkedList<>();
+ ops2.add(entry2);
+
+ final TestOperation op1 = new TestOperation(ops1);
+ final TestOperation sameAsOp1 = new TestOperation(ops1);
+ final TestOperation op2 = new TestOperation(ops2);
+
+ new EqualsTester()
+ .addEqualityGroup(op1, sameAsOp1)
+ .addEqualityGroup(op2)
+ .testEquals();
+ }
+
+ /**
+ * Tests the constructors for a BatchOperation.
+ */
+ @Test
+ public void testConstruction() {
+ final List<TestEntry> ops = new LinkedList<>();
+ ops.add(entry2);
+
+ final TestOperation op1 = new TestOperation();
+ assertThat(op1.size(), is(0));
+ assertThat(op1.getOperations(), hasSize(0));
+
+ final TestOperation op2 = new TestOperation(ops);
+ op1.addOperation(entry1);
+ op1.addAll(op2);
+ assertThat(op1.size(), is(2));
+ assertThat(op1.getOperations(), hasSize(2));
+
+ op2.clear();
+ assertThat(op2.size(), is(0));
+ assertThat(op2.getOperations(), hasSize(0));
+ }
+
+ /**
+ * Tests the constructor for BatchOperationEntries.
+ */
+ @Test
+ public void testEntryConstruction() {
+ final TestEntry entry = new TestEntry(TestType.OP3, new TestTarget(3));
+
+ assertThat(entry.operator(), is(TestType.OP3));
+ assertThat(entry.target().id, is(3));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowEntryTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowEntryTest.java
new file mode 100644
index 00000000..412acb62
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowEntryTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.flow;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.onosproject.net.intent.IntentTestsMocks;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Unit tests for the DefaultFlowEntry class.
+ */
+public class DefaultFlowEntryTest {
+ private static final IntentTestsMocks.MockSelector SELECTOR =
+ new IntentTestsMocks.MockSelector();
+ private static final IntentTestsMocks.MockTreatment TREATMENT =
+ new IntentTestsMocks.MockTreatment();
+
+ private static DefaultFlowEntry makeFlowEntry(int uniqueValue) {
+ FlowRule rule = DefaultFlowRule.builder()
+ .forDevice(did("id" + Integer.toString(uniqueValue)))
+ .withSelector(SELECTOR)
+ .withTreatment(TREATMENT)
+ .withPriority(uniqueValue)
+ .withCookie(uniqueValue)
+ .makeTemporary(uniqueValue)
+ .build();
+
+ return new DefaultFlowEntry(rule, FlowEntry.FlowEntryState.ADDED,
+ uniqueValue, uniqueValue, uniqueValue);
+ }
+
+ final DefaultFlowEntry defaultFlowEntry1 = makeFlowEntry(1);
+ final DefaultFlowEntry sameAsDefaultFlowEntry1 = makeFlowEntry(1);
+ final DefaultFlowEntry defaultFlowEntry2 = makeFlowEntry(2);
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(defaultFlowEntry1, sameAsDefaultFlowEntry1)
+ .addEqualityGroup(defaultFlowEntry2)
+ .testEquals();
+ }
+
+ /**
+ * Tests the construction of a default flow entry from a device id.
+ */
+ @Test
+ public void testDeviceBasedObject() {
+ assertThat(defaultFlowEntry1.deviceId(), is(did("id1")));
+ assertThat(defaultFlowEntry1.selector(), is(SELECTOR));
+ assertThat(defaultFlowEntry1.treatment(), is(TREATMENT));
+ assertThat(defaultFlowEntry1.timeout(), is(1));
+ assertThat(defaultFlowEntry1.life(), is(1L));
+ assertThat(defaultFlowEntry1.packets(), is(1L));
+ assertThat(defaultFlowEntry1.bytes(), is(1L));
+ assertThat(defaultFlowEntry1.state(), is(FlowEntry.FlowEntryState.ADDED));
+ assertThat(defaultFlowEntry1.lastSeen(),
+ greaterThan(System.currentTimeMillis() -
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+ }
+
+ /**
+ * Tests the setters on a default flow entry object.
+ */
+ @Test
+ public void testSetters() {
+ final DefaultFlowEntry entry = makeFlowEntry(1);
+
+ entry.setLastSeen();
+ entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
+ entry.setPackets(11);
+ entry.setBytes(22);
+ entry.setLife(33);
+
+ assertThat(entry.deviceId(), is(did("id1")));
+ assertThat(entry.selector(), is(SELECTOR));
+ assertThat(entry.treatment(), is(TREATMENT));
+ assertThat(entry.timeout(), is(1));
+ assertThat(entry.life(), is(33L));
+ assertThat(entry.packets(), is(11L));
+ assertThat(entry.bytes(), is(22L));
+ assertThat(entry.state(), is(FlowEntry.FlowEntryState.PENDING_REMOVE));
+ assertThat(entry.lastSeen(),
+ greaterThan(System.currentTimeMillis() -
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+ }
+
+ /**
+ * Tests a default flow rule built for an error.
+ */
+ @Test
+ public void testErrorObject() {
+ final DefaultFlowEntry errorEntry =
+ new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(1),
+ 111,
+ 222);
+ assertThat(errorEntry.errType(), is(111));
+ assertThat(errorEntry.errCode(), is(222));
+ assertThat(errorEntry.state(), is(FlowEntry.FlowEntryState.FAILED));
+ assertThat(errorEntry.lastSeen(),
+ greaterThan(System.currentTimeMillis() -
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+ }
+
+ /**
+ * Tests a default flow entry constructed from a flow rule.
+ */
+ @Test
+ public void testFlowBasedObject() {
+ final DefaultFlowEntry entry =
+ new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(1));
+ assertThat(entry.priority(), is(1));
+ assertThat(entry.appId(), is((short) 0));
+ assertThat(entry.lastSeen(),
+ greaterThan(System.currentTimeMillis() -
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+ }
+
+ /**
+ * Tests a default flow entry constructed from a flow rule plus extra
+ * parameters.
+ */
+ @Test
+ public void testFlowBasedObjectWithParameters() {
+ final DefaultFlowEntry entry =
+ new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(33),
+ FlowEntry.FlowEntryState.REMOVED,
+ 101, 102, 103);
+ assertThat(entry.state(), is(FlowEntry.FlowEntryState.REMOVED));
+ assertThat(entry.life(), is(101L));
+ assertThat(entry.packets(), is(102L));
+ assertThat(entry.bytes(), is(103L));
+ assertThat(entry.lastSeen(),
+ greaterThan(System.currentTimeMillis() -
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowRuleTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowRuleTest.java
new file mode 100644
index 00000000..62acd16a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultFlowRuleTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.flow;
+
+import org.junit.Test;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.net.intent.IntentTestsMocks;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Unit tests for the default flow rule class.
+ */
+public class DefaultFlowRuleTest {
+ private static final IntentTestsMocks.MockSelector SELECTOR =
+ new IntentTestsMocks.MockSelector();
+ private static final IntentTestsMocks.MockTreatment TREATMENT =
+ new IntentTestsMocks.MockTreatment();
+
+ private static byte [] b = new byte[3];
+ private static FlowRuleExtPayLoad payLoad = FlowRuleExtPayLoad.flowRuleExtPayLoad(b);
+ final FlowRule flowRule1 = new IntentTestsMocks.MockFlowRule(1, payLoad);
+ final FlowRule sameAsFlowRule1 = new IntentTestsMocks.MockFlowRule(1, payLoad);
+ final DefaultFlowRule defaultFlowRule1 = new DefaultFlowRule(flowRule1);
+ final DefaultFlowRule sameAsDefaultFlowRule1 = new DefaultFlowRule(sameAsFlowRule1);
+
+ /**
+ * Checks that the DefaultFlowRule class is immutable but can be inherited
+ * from.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutableBaseClass(DefaultFlowRule.class);
+ }
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(defaultFlowRule1, sameAsDefaultFlowRule1)
+ .testEquals();
+ }
+
+ /**
+ * Tests creation of a DefaultFlowRule using a FlowRule constructor.
+ */
+ @Test
+ public void testCreationFromFlowRule() {
+ assertThat(defaultFlowRule1.deviceId(), is(flowRule1.deviceId()));
+ assertThat(defaultFlowRule1.appId(), is(flowRule1.appId()));
+ assertThat(defaultFlowRule1.id(), is(flowRule1.id()));
+ assertThat(defaultFlowRule1.isPermanent(), is(flowRule1.isPermanent()));
+ assertThat(defaultFlowRule1.priority(), is(flowRule1.priority()));
+ assertThat(defaultFlowRule1.selector(), is(flowRule1.selector()));
+ assertThat(defaultFlowRule1.treatment(), is(flowRule1.treatment()));
+ assertThat(defaultFlowRule1.timeout(), is(flowRule1.timeout()));
+ assertThat(defaultFlowRule1.payLoad(), is(flowRule1.payLoad()));
+ }
+
+ /**
+ * Tests creation of a DefaultFlowRule using a FlowId constructor.
+ */
+
+ @Test
+ public void testCreationWithFlowId() {
+ final FlowRule rule =
+ DefaultFlowRule.builder()
+ .forDevice(did("1"))
+ .withSelector(SELECTOR)
+ .withTreatment(TREATMENT)
+ .withPriority(22)
+ .makeTemporary(44)
+ .fromApp(APP_ID)
+ .build();
+
+ assertThat(rule.deviceId(), is(did("1")));
+ assertThat(rule.isPermanent(), is(false));
+ assertThat(rule.priority(), is(22));
+ assertThat(rule.selector(), is(SELECTOR));
+ assertThat(rule.treatment(), is(TREATMENT));
+ assertThat(rule.timeout(), is(44));
+ }
+
+
+ /**
+ * Tests creation of a DefaultFlowRule using a PayLoad constructor.
+ */
+ @Test
+ public void testCreationWithPayLoadByFlowTable() {
+ final DefaultFlowRule rule =
+ new DefaultFlowRule(did("1"), null,
+ null, 22, APP_ID,
+ 44, false, payLoad);
+ assertThat(rule.deviceId(), is(did("1")));
+ assertThat(rule.isPermanent(), is(false));
+ assertThat(rule.priority(), is(22));
+ assertThat(rule.timeout(), is(44));
+ assertThat(defaultFlowRule1.payLoad(), is(payLoad));
+ }
+
+ /**
+ * Tests creation of a DefaultFlowRule using a PayLoad constructor.
+ */
+ @Test
+ public void testCreationWithPayLoadByGroupTable() {
+ final DefaultFlowRule rule =
+ new DefaultFlowRule(did("1"), null,
+ null, 22, APP_ID, new DefaultGroupId(0),
+ 44, false, payLoad);
+ assertThat(rule.deviceId(), is(did("1")));
+ assertThat(rule.isPermanent(), is(false));
+ assertThat(rule.priority(), is(22));
+ assertThat(rule.timeout(), is(44));
+ assertThat(rule.groupId(), is(new DefaultGroupId(0)));
+ assertThat(defaultFlowRule1.payLoad(), is(payLoad));
+ }
+ /**
+ * Tests the creation of a DefaultFlowRule using an AppId constructor.
+ */
+ @Test
+ public void testCreationWithAppId() {
+ final FlowRule rule =
+ DefaultFlowRule.builder()
+ .forDevice(did("1"))
+ .withSelector(SELECTOR)
+ .withTreatment(TREATMENT)
+ .withPriority(22)
+ .fromApp(APP_ID)
+ .makeTemporary(44)
+ .build();
+
+ assertThat(rule.deviceId(), is(did("1")));
+ assertThat(rule.isPermanent(), is(false));
+ assertThat(rule.priority(), is(22));
+ assertThat(rule.selector(), is(SELECTOR));
+ assertThat(rule.treatment(), is(TREATMENT));
+ assertThat(rule.timeout(), is(44));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficSelectorTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficSelectorTest.java
new file mode 100644
index 00000000..b871397b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficSelectorTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Test;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.flow.criteria.Criterion.Type;
+
+/**
+ * Unit tests for default traffic selector class.
+ */
+public class DefaultTrafficSelectorTest {
+
+ /**
+ * Checks that the DefaultFlowRule class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(DefaultTrafficSelector.class);
+ }
+
+ /**
+ * Tests equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ final short one = 1;
+ final short two = 2;
+
+ final TrafficSelector selector1 = DefaultTrafficSelector.builder()
+ .add(Criteria.matchLambda(new IndexedLambda(one)))
+ .build();
+ final TrafficSelector sameAsSelector1 = DefaultTrafficSelector.builder()
+ .add(Criteria.matchLambda(new IndexedLambda(one)))
+ .build();
+ final TrafficSelector selector2 = DefaultTrafficSelector.builder()
+ .add(Criteria.matchLambda(new IndexedLambda(two)))
+ .build();
+
+ new EqualsTester()
+ .addEqualityGroup(selector1, sameAsSelector1)
+ .addEqualityGroup(selector2)
+ .testEquals();
+ }
+
+ /**
+ * Hamcrest matcher to check that a selector contains a
+ * Criterion with the specified type.
+ */
+ public static final class CriterionExistsMatcher
+ extends TypeSafeMatcher<TrafficSelector> {
+ private final Criterion.Type type;
+
+ /**
+ * Constructs a matcher for the given criterion type.
+ *
+ * @param typeValue criterion type to match
+ */
+ public CriterionExistsMatcher(Criterion.Type typeValue) {
+ type = typeValue;
+ }
+
+ @Override
+ public boolean matchesSafely(TrafficSelector selector) {
+ final Set<Criterion> criteria = selector.criteria();
+
+ return notNullValue().matches(criteria) &&
+ hasSize(1).matches(criteria) &&
+ notNullValue().matches(selector.getCriterion(type));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("a criterion with type \" ").
+ appendText(type.toString()).
+ appendText("\"");
+ }
+ }
+
+
+ /**
+ * Creates a criterion type matcher. Returns a matcher
+ * for a criterion with the given type.
+ *
+ * @param type type of Criterion to match
+ * @return Matcher object
+ */
+ @Factory
+ public static Matcher<TrafficSelector> hasCriterionWithType(Criterion.Type type) {
+ return new CriterionExistsMatcher(type);
+ }
+
+
+ /**
+ * Tests the builder functions that add specific criteria.
+ */
+ @Test
+ public void testCriteriaCreation() {
+ TrafficSelector selector;
+
+ final long longValue = 0x12345678;
+ final int intValue = 22;
+ final short shortValue = 33;
+ final byte byteValue = 44;
+ final byte dscpValue = 0xf;
+ final byte ecnValue = 3;
+ final MacAddress macValue = MacAddress.valueOf("11:22:33:44:55:66");
+ final IpPrefix ipPrefixValue = IpPrefix.valueOf("192.168.1.0/24");
+ final IpPrefix ipv6PrefixValue = IpPrefix.valueOf("fe80::1/64");
+ final Ip6Address ipv6AddressValue = Ip6Address.valueOf("fe80::1");
+
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(11)).build();
+ assertThat(selector, hasCriterionWithType(Type.IN_PORT));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchInPhyPort(PortNumber.portNumber(11)).build();
+ assertThat(selector, hasCriterionWithType(Type.IN_PHY_PORT));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchMetadata(longValue).build();
+ assertThat(selector, hasCriterionWithType(Type.METADATA));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchEthDst(macValue).build();
+ assertThat(selector, hasCriterionWithType(Type.ETH_DST));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchEthSrc(macValue).build();
+ assertThat(selector, hasCriterionWithType(Type.ETH_SRC));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(shortValue).build();
+ assertThat(selector, hasCriterionWithType(Type.ETH_TYPE));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchVlanId(VlanId.vlanId(shortValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.VLAN_VID));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchVlanPcp(byteValue).build();
+ assertThat(selector, hasCriterionWithType(Type.VLAN_PCP));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPDscp(dscpValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IP_DSCP));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPEcn(ecnValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IP_ECN));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPProtocol(byteValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IP_PROTO));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPSrc(ipPrefixValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV4_SRC));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPDst(ipPrefixValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV4_DST));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchTcpSrc(TpPort.tpPort(intValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.TCP_SRC));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchTcpDst(TpPort.tpPort(intValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.TCP_DST));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchUdpSrc(TpPort.tpPort(intValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.UDP_SRC));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchUdpDst(TpPort.tpPort(intValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.UDP_DST));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchSctpSrc(TpPort.tpPort(intValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.SCTP_SRC));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchSctpDst(TpPort.tpPort(intValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.SCTP_DST));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIcmpType(byteValue).build();
+ assertThat(selector, hasCriterionWithType(Type.ICMPV4_TYPE));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIcmpCode(byteValue).build();
+ assertThat(selector, hasCriterionWithType(Type.ICMPV4_CODE));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6Src(ipv6PrefixValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_SRC));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6Dst(ipv6PrefixValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_DST));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6FlowLabel(intValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_FLABEL));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIcmpv6Type(byteValue).build();
+ assertThat(selector, hasCriterionWithType(Type.ICMPV6_TYPE));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6NDTargetAddress(ipv6AddressValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_ND_TARGET));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6NDSourceLinkLayerAddress(macValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_ND_SLL));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6NDTargetLinkLayerAddress(macValue).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_ND_TLL));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchMplsLabel(MplsLabel.mplsLabel(3)).build();
+ assertThat(selector, hasCriterionWithType(Type.MPLS_LABEL));
+
+ selector = DefaultTrafficSelector.builder()
+ .matchIPv6ExthdrFlags(Criterion.IPv6ExthdrFlags.NONEXT.getValue()).build();
+ assertThat(selector, hasCriterionWithType(Type.IPV6_EXTHDR));
+
+ selector = DefaultTrafficSelector.builder()
+ .add(Criteria.matchLambda(new IndexedLambda(shortValue))).build();
+ assertThat(selector, hasCriterionWithType(Type.OCH_SIGID));
+
+ selector = DefaultTrafficSelector.builder()
+ .add(Criteria.matchOpticalSignalType(shortValue)).build();
+ assertThat(selector, hasCriterionWithType(Type.OCH_SIGTYPE));
+ }
+
+ /**
+ * Tests the traffic selector builder.
+ */
+ @Test
+ public void testTrafficSelectorBuilder() {
+ TrafficSelector selector;
+ final short shortValue = 33;
+
+ final TrafficSelector baseSelector = DefaultTrafficSelector.builder()
+ .add(Criteria.matchLambda(new IndexedLambda(shortValue))).build();
+ selector = DefaultTrafficSelector.builder(baseSelector)
+ .build();
+ assertThat(selector, hasCriterionWithType(Type.OCH_SIGID));
+
+ final Criterion criterion = Criteria.matchLambda(shortValue);
+ selector = DefaultTrafficSelector.builder()
+ .add(criterion).build();
+ assertThat(selector, hasCriterionWithType(Type.OCH_SIGID));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficTreatmentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficTreatmentTest.java
new file mode 100644
index 00000000..288f5f2f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/DefaultTrafficTreatmentTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Unit tests for the DefaultTrafficTreatment class.
+ */
+public class DefaultTrafficTreatmentTest {
+
+ // Tests for the nested Builder class
+
+ /**
+ * Tests that the Builder constructors return equivalent objects
+ * when given the same data.
+ */
+ @Test
+ public void testTreatmentBuilderConstructors() {
+ final TrafficTreatment treatment1 =
+ DefaultTrafficTreatment.builder()
+ .add(Instructions.modL0Lambda(new IndexedLambda(4)))
+ .build();
+ final TrafficTreatment treatment2 =
+ DefaultTrafficTreatment.builder(treatment1).build();
+ assertThat(treatment1, is(equalTo(treatment2)));
+ }
+
+ /**
+ * Tests methods defined on the Builder.
+ */
+ @Test
+ public void testBuilderMethods() {
+ final Instruction instruction1 =
+ Instructions.modL0Lambda(new IndexedLambda(4));
+
+ final TrafficTreatment.Builder builder1 =
+ DefaultTrafficTreatment.builder()
+ .add(instruction1)
+ .setEthDst(MacAddress.BROADCAST)
+ .setEthSrc(MacAddress.BROADCAST)
+ .setIpDst(IpAddress.valueOf("1.1.1.1"))
+ .setIpSrc(IpAddress.valueOf("2.2.2.2"))
+ .add(Instructions.modL0Lambda(new IndexedLambda(4)))
+ .setOutput(PortNumber.portNumber(2))
+ .setVlanId(VlanId.vlanId((short) 4))
+ .setVlanPcp((byte) 3);
+
+ final TrafficTreatment treatment1 = builder1.build();
+
+ final List<Instruction> instructions1 = treatment1.immediate();
+ assertThat(instructions1, hasSize(9));
+
+ builder1.drop();
+ builder1.add(instruction1);
+
+ final List<Instruction> instructions2 = builder1.build().immediate();
+ assertThat(instructions2, hasSize(11));
+
+ builder1.deferred()
+ .popVlan()
+ .pushVlan()
+ .setVlanId(VlanId.vlanId((short) 5));
+
+ final List<Instruction> instructions3 = builder1.build().immediate();
+ assertThat(instructions3, hasSize(11));
+ final List<Instruction> instructions4 = builder1.build().deferred();
+ assertThat(instructions4, hasSize(3));
+ }
+
+ /**
+ * Tests equals(), hashCode() and toString() methods of
+ * DefaultTrafficTreatment.
+ */
+ @Test
+ public void testEquals() {
+ final IndexedLambda lambda1 = new IndexedLambda(4);
+ final IndexedLambda lambda2 = new IndexedLambda(5);
+ final TrafficTreatment treatment1 =
+ DefaultTrafficTreatment.builder()
+ .add(Instructions.modL0Lambda(lambda1))
+ .build();
+ final TrafficTreatment sameAsTreatment1 =
+ DefaultTrafficTreatment.builder()
+ .add(Instructions.modL0Lambda(lambda1))
+ .build();
+ final TrafficTreatment treatment2 =
+ DefaultTrafficTreatment.builder()
+ .add(Instructions.modL0Lambda(lambda2))
+ .build();
+ new EqualsTester()
+ .addEqualityGroup(treatment1, sameAsTreatment1)
+ .addEqualityGroup(treatment2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowIdTest.java
new file mode 100644
index 00000000..263d4031
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowIdTest.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.net.flow;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for flow id class.
+ */
+public class FlowIdTest {
+
+ final FlowId flowId1 = FlowId.valueOf(1);
+ final FlowId sameAsFlowId1 = FlowId.valueOf(1);
+ final FlowId flowId2 = FlowId.valueOf(2);
+
+ /**
+ * Checks that the FlowId class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(FlowId.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(flowId1, sameAsFlowId1)
+ .addEqualityGroup(flowId2)
+ .testEquals();
+ }
+
+ /**
+ * Checks the construction of a FlowId object.
+ */
+ @Test
+ public void testConstruction() {
+ final long flowIdValue = 7777L;
+ final FlowId flowId = FlowId.valueOf(flowIdValue);
+ assertThat(flowId, is(notNullValue()));
+ assertThat(flowId.value(), is(flowIdValue));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java
new file mode 100644
index 00000000..4fa3492a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.util.LinkedList;
+
+import org.junit.Test;
+import org.onosproject.net.intent.IntentTestsMocks;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Unit tests for flow rule batch classes.
+ */
+public class FlowRuleBatchOperationTest {
+
+ /**
+ * Tests the equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ final FlowRule rule = new IntentTestsMocks.MockFlowRule(1);
+ final FlowRuleBatchEntry entry1 = new FlowRuleBatchEntry(
+ FlowRuleBatchEntry.FlowRuleOperation.ADD, rule);
+ final FlowRuleBatchEntry entry2 = new FlowRuleBatchEntry(
+ FlowRuleBatchEntry.FlowRuleOperation.MODIFY, rule);
+ final FlowRuleBatchEntry entry3 = new FlowRuleBatchEntry(
+ FlowRuleBatchEntry.FlowRuleOperation.REMOVE, rule);
+ final LinkedList<FlowRuleBatchEntry> ops1 = new LinkedList<>();
+ ops1.add(entry1);
+ final LinkedList<FlowRuleBatchEntry> ops2 = new LinkedList<>();
+ ops1.add(entry2);
+ final LinkedList<FlowRuleBatchEntry> ops3 = new LinkedList<>();
+ ops3.add(entry3);
+
+ final FlowRuleBatchOperation operation1 = new FlowRuleBatchOperation(ops1, null, 0);
+ final FlowRuleBatchOperation sameAsOperation1 = new FlowRuleBatchOperation(ops1, null, 0);
+ final FlowRuleBatchOperation operation2 = new FlowRuleBatchOperation(ops2, null, 0);
+ final FlowRuleBatchOperation operation3 = new FlowRuleBatchOperation(ops3, null, 0);
+
+ new EqualsTester()
+ .addEqualityGroup(operation1, sameAsOperation1)
+ .addEqualityGroup(operation2)
+ .addEqualityGroup(operation3)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java
new file mode 100644
index 00000000..b379a6ec
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.net.intent.IntentTestsMocks;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation.ADD;
+import static org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation.REMOVE;
+
+/**
+ * Unit tests for the FlowRuleBatchRequest class.
+ */
+public class FlowRuleBatchRequestTest {
+
+ /**
+ * Tests that construction of FlowRuleBatchRequest objects returns the
+ * correct objects.
+ */
+ @Test
+ public void testConstruction() {
+ final FlowRule rule1 = new IntentTestsMocks.MockFlowRule(1);
+ final FlowRule rule2 = new IntentTestsMocks.MockFlowRule(2);
+ final Set<FlowRuleBatchEntry> batch = new HashSet<>();
+ batch.add(new FlowRuleBatchEntry(ADD, rule1));
+
+ batch.add(new FlowRuleBatchEntry(REMOVE, rule2));
+
+
+ final FlowRuleBatchRequest request =
+ new FlowRuleBatchRequest(1, batch);
+
+ assertThat(request.ops(), hasSize(2));
+ assertThat(request.batchId(), is(1L));
+
+ final FlowRuleBatchOperation op = request.asBatchOperation(rule1.deviceId());
+ assertThat(op.size(), is(2));
+
+ final List<FlowRuleBatchEntry> ops = op.getOperations();
+ assertThat(ops, hasSize(2));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleEventTest.java
new file mode 100644
index 00000000..2d79c893
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleEventTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.flow;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.onosproject.event.AbstractEventTest;
+import org.onosproject.net.intent.IntentTestsMocks;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Unit Tests for the FlowRuleEvent class.
+ */
+public class FlowRuleEventTest extends AbstractEventTest {
+
+ @Test
+ public void testEquals() {
+ final FlowRule flowRule1 = new IntentTestsMocks.MockFlowRule(1);
+ final FlowRule flowRule2 = new IntentTestsMocks.MockFlowRule(2);
+ final long time = 123L;
+ final FlowRuleEvent event1 =
+ new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, flowRule1, time);
+ final FlowRuleEvent sameAsEvent1 =
+ new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, flowRule1, time);
+ final FlowRuleEvent event2 =
+ new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED,
+ flowRule2, time);
+
+ // Equality for events is based on Object, these should all compare
+ // as different.
+ new EqualsTester()
+ .addEqualityGroup(event1)
+ .addEqualityGroup(sameAsEvent1)
+ .addEqualityGroup(event2)
+ .testEquals();
+ }
+
+ /**
+ * Tests the constructor where a time is passed in.
+ */
+ @Test
+ public void testTimeConstructor() {
+ final long time = 123L;
+ final FlowRule flowRule = new IntentTestsMocks.MockFlowRule(1);
+ final FlowRuleEvent event =
+ new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, flowRule, time);
+ validateEvent(event, FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, flowRule, time);
+ }
+
+ /**
+ * Tests the constructor with the default time value.
+ */
+ @Test
+ public void testConstructor() {
+ final long time = System.currentTimeMillis();
+ final FlowRule flowRule = new IntentTestsMocks.MockFlowRule(1);
+ final FlowRuleEvent event =
+ new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, flowRule);
+ validateEvent(event, FlowRuleEvent.Type.RULE_UPDATED, flowRule, time,
+ time + TimeUnit.SECONDS.toMillis(30));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleExtPayLoadTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleExtPayLoadTest.java
new file mode 100644
index 00000000..30326a2e
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleExtPayLoadTest.java
@@ -0,0 +1,36 @@
+package org.onosproject.net.flow;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+/**
+ * Test for FlowRuleExtPayLoad.
+ */
+public class FlowRuleExtPayLoadTest {
+ final byte[] b = new byte[3];
+ final byte[] b1 = new byte[5];
+ final FlowRuleExtPayLoad payLoad1 = FlowRuleExtPayLoad.flowRuleExtPayLoad(b);
+ final FlowRuleExtPayLoad sameAsPayLoad1 = FlowRuleExtPayLoad.flowRuleExtPayLoad(b);
+ final FlowRuleExtPayLoad payLoad2 = FlowRuleExtPayLoad.flowRuleExtPayLoad(b1);
+
+ /**
+ * Checks that the FlowRuleExtPayLoad class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(FlowRuleExtPayLoad.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(payLoad1, sameAsPayLoad1)
+ .addEqualityGroup(payLoad2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java
new file mode 100644
index 00000000..c7b78791
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.flow;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Test adapter for flow rule service.
+ */
+public class FlowRuleServiceAdapter implements FlowRuleService {
+ @Override
+ public int getFlowRuleCount() {
+ return 0;
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public void applyFlowRules(FlowRule... flowRules) {
+
+ }
+
+ @Override
+ public void removeFlowRules(FlowRule... flowRules) {
+
+ }
+
+ @Override
+ public void removeFlowRulesById(ApplicationId appId) {
+
+ }
+
+ @Override
+ public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
+ return null;
+ }
+
+ @Override
+ public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) {
+ return null;
+ }
+
+ @Override
+ public void apply(FlowRuleOperations ops) {
+
+ }
+
+ @Override
+ public void addListener(FlowRuleListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(FlowRuleListener listener) {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/criteria/CriteriaTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/criteria/CriteriaTest.java
new file mode 100644
index 00000000..ee294f6f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/criteria/CriteriaTest.java
@@ -0,0 +1,1138 @@
+/*
+ * 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.criteria;
+
+import org.junit.Test;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.PortNumber;
+
+import com.google.common.testing.EqualsTester;
+import org.onosproject.net.OchSignalType;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onlab.junit.UtilityClassChecker.assertThatClassIsUtility;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Unit tests for the Criteria class and its subclasses.
+ */
+public class CriteriaTest {
+
+ final PortNumber port1 = portNumber(1);
+ final PortNumber port2 = portNumber(2);
+
+ Criterion matchInPort1 = Criteria.matchInPort(port1);
+ Criterion sameAsMatchInPort1 = Criteria.matchInPort(port1);
+ Criterion matchInPort2 = Criteria.matchInPort(port2);
+
+ Criterion matchInPhyPort1 = Criteria.matchInPhyPort(port1);
+ Criterion sameAsMatchInPhyPort1 = Criteria.matchInPhyPort(port1);
+ Criterion matchInPhyPort2 = Criteria.matchInPhyPort(port2);
+
+ long metadata1 = 1;
+ long metadata2 = 2;
+ Criterion matchMetadata1 = Criteria.matchMetadata(metadata1);
+ Criterion sameAsMatchMetadata1 = Criteria.matchMetadata(metadata1);
+ Criterion matchMetadata2 = Criteria.matchMetadata(metadata2);
+
+ private static final String MAC1 = "00:00:00:00:00:01";
+ private static final String MAC2 = "00:00:00:00:00:02";
+ private MacAddress mac1 = MacAddress.valueOf(MAC1);
+ private MacAddress mac2 = MacAddress.valueOf(MAC2);
+ Criterion matchEth1 = Criteria.matchEthSrc(mac1);
+ Criterion sameAsMatchEth1 = Criteria.matchEthSrc(mac1);
+ Criterion matchEth2 = Criteria.matchEthDst(mac2);
+
+ int ethType1 = 1;
+ int ethType2 = 2;
+ Criterion matchEthType1 = Criteria.matchEthType(ethType1);
+ Criterion sameAsMatchEthType1 = Criteria.matchEthType(ethType1);
+ Criterion matchEthType2 = Criteria.matchEthType(ethType2);
+
+ short vlan1 = 1;
+ short vlan2 = 2;
+ VlanId vlanId1 = VlanId.vlanId(vlan1);
+ VlanId vlanId2 = VlanId.vlanId(vlan2);
+ Criterion matchVlanId1 = Criteria.matchVlanId(vlanId1);
+ Criterion sameAsMatchVlanId1 = Criteria.matchVlanId(vlanId1);
+ Criterion matchVlanId2 = Criteria.matchVlanId(vlanId2);
+
+ byte vlanPcp1 = 1;
+ byte vlanPcp2 = 2;
+ Criterion matchVlanPcp1 = Criteria.matchVlanPcp(vlanPcp1);
+ Criterion sameAsMatchVlanPcp1 = Criteria.matchVlanPcp(vlanPcp1);
+ Criterion matchVlanPcp2 = Criteria.matchVlanPcp(vlanPcp2);
+
+ byte ipDscp1 = 1;
+ byte ipDscp2 = 2;
+ Criterion matchIpDscp1 = Criteria.matchIPDscp(ipDscp1);
+ Criterion sameAsMatchIpDscp1 = Criteria.matchIPDscp(ipDscp1);
+ Criterion matchIpDscp2 = Criteria.matchIPDscp(ipDscp2);
+
+ byte ipEcn1 = 1;
+ byte ipEcn2 = 2;
+ Criterion matchIpEcn1 = Criteria.matchIPEcn(ipEcn1);
+ Criterion sameAsMatchIpEcn1 = Criteria.matchIPEcn(ipEcn1);
+ Criterion matchIpEcn2 = Criteria.matchIPEcn(ipEcn2);
+
+ short protocol1 = 1;
+ short protocol2 = 2;
+ Criterion matchIpProtocol1 = Criteria.matchIPProtocol(protocol1);
+ Criterion sameAsMatchIpProtocol1 = Criteria.matchIPProtocol(protocol1);
+ Criterion matchIpProtocol2 = Criteria.matchIPProtocol(protocol2);
+
+ private static final String IP1 = "1.2.3.4/24";
+ private static final String IP2 = "5.6.7.8/24";
+ private static final String IPV61 = "fe80::1/64";
+ private static final String IPV62 = "fc80::2/64";
+ private IpPrefix ip1 = IpPrefix.valueOf(IP1);
+ private IpPrefix ip2 = IpPrefix.valueOf(IP2);
+ private IpPrefix ipv61 = IpPrefix.valueOf(IPV61);
+ private IpPrefix ipv62 = IpPrefix.valueOf(IPV62);
+ Criterion matchIp1 = Criteria.matchIPSrc(ip1);
+ Criterion sameAsMatchIp1 = Criteria.matchIPSrc(ip1);
+ Criterion matchIp2 = Criteria.matchIPSrc(ip2);
+ Criterion matchIpv61 = Criteria.matchIPSrc(ipv61);
+ Criterion sameAsMatchIpv61 = Criteria.matchIPSrc(ipv61);
+ Criterion matchIpv62 = Criteria.matchIPSrc(ipv62);
+
+ private TpPort tpPort1 = TpPort.tpPort(1);
+ private TpPort tpPort2 = TpPort.tpPort(2);
+ Criterion matchTcpPort1 = Criteria.matchTcpSrc(tpPort1);
+ Criterion sameAsMatchTcpPort1 = Criteria.matchTcpSrc(tpPort1);
+ Criterion matchTcpPort2 = Criteria.matchTcpDst(tpPort2);
+
+ Criterion matchUdpPort1 = Criteria.matchUdpSrc(tpPort1);
+ Criterion sameAsMatchUdpPort1 = Criteria.matchUdpSrc(tpPort1);
+ Criterion matchUdpPort2 = Criteria.matchUdpDst(tpPort2);
+
+ Criterion matchSctpPort1 = Criteria.matchSctpSrc(tpPort1);
+ Criterion sameAsMatchSctpPort1 = Criteria.matchSctpSrc(tpPort1);
+ Criterion matchSctpPort2 = Criteria.matchSctpDst(tpPort2);
+
+ short icmpType1 = 1;
+ short icmpType2 = 2;
+ Criterion matchIcmpType1 = Criteria.matchIcmpType(icmpType1);
+ Criterion sameAsMatchIcmpType1 = Criteria.matchIcmpType(icmpType1);
+ Criterion matchIcmpType2 = Criteria.matchIcmpType(icmpType2);
+
+ short icmpCode1 = 1;
+ short icmpCode2 = 2;
+ Criterion matchIcmpCode1 = Criteria.matchIcmpCode(icmpCode1);
+ Criterion sameAsMatchIcmpCode1 = Criteria.matchIcmpCode(icmpCode1);
+ Criterion matchIcmpCode2 = Criteria.matchIcmpCode(icmpCode2);
+
+ int flowLabel1 = 1;
+ int flowLabel2 = 2;
+ Criterion matchFlowLabel1 = Criteria.matchIPv6FlowLabel(flowLabel1);
+ Criterion sameAsMatchFlowLabel1 = Criteria.matchIPv6FlowLabel(flowLabel1);
+ Criterion matchFlowLabel2 = Criteria.matchIPv6FlowLabel(flowLabel2);
+
+ short icmpv6Type1 = 1;
+ short icmpv6Type2 = 2;
+ Criterion matchIcmpv6Type1 = Criteria.matchIcmpv6Type(icmpv6Type1);
+ Criterion sameAsMatchIcmpv6Type1 = Criteria.matchIcmpv6Type(icmpv6Type1);
+ Criterion matchIcmpv6Type2 = Criteria.matchIcmpv6Type(icmpv6Type2);
+
+ short icmpv6Code1 = 1;
+ short icmpv6Code2 = 2;
+ Criterion matchIcmpv6Code1 = Criteria.matchIcmpv6Code(icmpv6Code1);
+ Criterion sameAsMatchIcmpv6Code1 = Criteria.matchIcmpv6Code(icmpv6Code1);
+ Criterion matchIcmpv6Code2 = Criteria.matchIcmpv6Code(icmpv6Code2);
+
+ private static final String IPV6_ADDR1 = "fe80::1";
+ private static final String IPV6_ADDR2 = "fe80::2";
+ private Ip6Address ip6TargetAddress1 = Ip6Address.valueOf(IPV6_ADDR1);
+ private Ip6Address ip6TargetAddress2 = Ip6Address.valueOf(IPV6_ADDR2);
+ Criterion matchIpv6TargetAddr1 =
+ Criteria.matchIPv6NDTargetAddress(ip6TargetAddress1);
+ Criterion sameAsMatchIpv6TargetAddr1 =
+ Criteria.matchIPv6NDTargetAddress(ip6TargetAddress1);
+ Criterion matchIpv6TargetAddr2 =
+ Criteria.matchIPv6NDTargetAddress(ip6TargetAddress2);
+
+ private static final String LL_MAC1 = "00:00:00:00:00:01";
+ private static final String LL_MAC2 = "00:00:00:00:00:02";
+ private MacAddress llMac1 = MacAddress.valueOf(LL_MAC1);
+ private MacAddress llMac2 = MacAddress.valueOf(LL_MAC2);
+ Criterion matchSrcLlAddr1 =
+ Criteria.matchIPv6NDSourceLinkLayerAddress(llMac1);
+ Criterion sameAsMatchSrcLlAddr1 =
+ Criteria.matchIPv6NDSourceLinkLayerAddress(llMac1);
+ Criterion matchSrcLlAddr2 =
+ Criteria.matchIPv6NDSourceLinkLayerAddress(llMac2);
+ Criterion matchTargetLlAddr1 =
+ Criteria.matchIPv6NDTargetLinkLayerAddress(llMac1);
+ Criterion sameAsMatchTargetLlAddr1 =
+ Criteria.matchIPv6NDTargetLinkLayerAddress(llMac1);
+ Criterion matchTargetLlAddr2 =
+ Criteria.matchIPv6NDTargetLinkLayerAddress(llMac2);
+
+ MplsLabel mpls1 = MplsLabel.mplsLabel(1);
+ MplsLabel mpls2 = MplsLabel.mplsLabel(2);
+ Criterion matchMpls1 = Criteria.matchMplsLabel(mpls1);
+ Criterion sameAsMatchMpls1 = Criteria.matchMplsLabel(mpls1);
+ Criterion matchMpls2 = Criteria.matchMplsLabel(mpls2);
+
+ long tunnelId1 = 1;
+ long tunnelId2 = 2;
+ Criterion matchTunnelId1 = Criteria.matchTunnelId(tunnelId1);
+ Criterion sameAsMatchTunnelId1 = Criteria.matchTunnelId(tunnelId1);
+ Criterion matchTunnelId2 = Criteria.matchTunnelId(tunnelId2);
+
+ int ipv6ExthdrFlags1 =
+ Criterion.IPv6ExthdrFlags.NONEXT.getValue() |
+ Criterion.IPv6ExthdrFlags.ESP.getValue() |
+ Criterion.IPv6ExthdrFlags.AUTH.getValue() |
+ Criterion.IPv6ExthdrFlags.DEST.getValue() |
+ Criterion.IPv6ExthdrFlags.FRAG.getValue() |
+ Criterion.IPv6ExthdrFlags.ROUTER.getValue() |
+ Criterion.IPv6ExthdrFlags.HOP.getValue() |
+ Criterion.IPv6ExthdrFlags.UNREP.getValue();
+ int ipv6ExthdrFlags2 = ipv6ExthdrFlags1 |
+ Criterion.IPv6ExthdrFlags.UNSEQ.getValue();
+ Criterion matchIpv6ExthdrFlags1 =
+ Criteria.matchIPv6ExthdrFlags(ipv6ExthdrFlags1);
+ Criterion sameAsMatchIpv6ExthdrFlags1 =
+ Criteria.matchIPv6ExthdrFlags(ipv6ExthdrFlags1);
+ Criterion matchIpv6ExthdrFlags2 =
+ Criteria.matchIPv6ExthdrFlags(ipv6ExthdrFlags2);
+
+ int lambda1 = 1;
+ int lambda2 = 2;
+ Criterion matchLambda1 = Criteria.matchLambda(lambda1);
+ Criterion sameAsMatchLambda1 = Criteria.matchLambda(lambda1);
+ Criterion matchLambda2 = Criteria.matchLambda(lambda2);
+
+ Criterion matchOchSignalType1 = Criteria.matchOchSignalType(OchSignalType.FIXED_GRID);
+ Criterion sameAsMatchOchSignalType1 = Criteria.matchOchSignalType(OchSignalType.FIXED_GRID);
+ Criterion matchOchSignalType2 = Criteria.matchOchSignalType(OchSignalType.FLEX_GRID);
+
+ Criterion matchIndexedLambda1 = Criteria.matchLambda(Lambda.indexedLambda(1));
+ Criterion sameAsMatchIndexedLambda1 = Criteria.matchLambda(Lambda.indexedLambda(1));
+ Criterion matchIndexedLambda2 = Criteria.matchLambda(Lambda.indexedLambda(2));
+
+ short signalLambda1 = 1;
+ short signalLambda2 = 2;
+ Criterion matchSignalLambda1 = Criteria.matchOpticalSignalType(signalLambda1);
+ Criterion sameAsMatchSignalLambda1 = Criteria.matchOpticalSignalType(signalLambda1);
+ Criterion matchSignalLambda2 = Criteria.matchOpticalSignalType(signalLambda2);
+
+ Criterion matchOchSignal1 =
+ Criteria.matchLambda(Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8));
+ Criterion sameAsMatchOchSignal1 =
+ Criteria.matchLambda(Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8));
+ Criterion matchOchSignal2 =
+ Criteria.matchLambda(Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, 4, 8));
+
+ /**
+ * Checks that a Criterion object has the proper type, and then converts
+ * it to the proper type.
+ *
+ * @param criterion Criterion object to convert
+ * @param type Enumerated type value for the Criterion class
+ * @param clazz Desired Criterion class
+ * @param <T> The type the caller wants returned
+ * @return converted object
+ */
+ @SuppressWarnings("unchecked")
+ private <T> T checkAndConvert(Criterion criterion, Criterion.Type type, Class clazz) {
+ assertThat(criterion, is(notNullValue()));
+ assertThat(criterion.type(), is(equalTo(type)));
+ assertThat(criterion, instanceOf(clazz));
+ return (T) criterion;
+ }
+
+ /**
+ * Check that the Criteria class is a valid utility class.
+ */
+ @Test
+ public void testCriteriaUtility() {
+ assertThatClassIsUtility(Criteria.class);
+ }
+
+ /**
+ * Check that the Criteria implementations are immutable.
+ */
+ @Test
+ public void testCriteriaImmutability() {
+ assertThatClassIsImmutable(PortCriterion.class);
+ assertThatClassIsImmutable(MetadataCriterion.class);
+ assertThatClassIsImmutable(EthCriterion.class);
+ assertThatClassIsImmutable(EthTypeCriterion.class);
+ assertThatClassIsImmutable(VlanIdCriterion.class);
+ assertThatClassIsImmutable(VlanPcpCriterion.class);
+ assertThatClassIsImmutable(IPDscpCriterion.class);
+ assertThatClassIsImmutable(IPEcnCriterion.class);
+ assertThatClassIsImmutable(IPProtocolCriterion.class);
+ assertThatClassIsImmutable(IPCriterion.class);
+ assertThatClassIsImmutable(TcpPortCriterion.class);
+ assertThatClassIsImmutable(UdpPortCriterion.class);
+ assertThatClassIsImmutable(SctpPortCriterion.class);
+ assertThatClassIsImmutable(IcmpTypeCriterion.class);
+ assertThatClassIsImmutable(IcmpCodeCriterion.class);
+ assertThatClassIsImmutable(IPv6FlowLabelCriterion.class);
+ assertThatClassIsImmutable(Icmpv6TypeCriterion.class);
+ assertThatClassIsImmutable(Icmpv6CodeCriterion.class);
+ assertThatClassIsImmutable(IPv6NDTargetAddressCriterion.class);
+ assertThatClassIsImmutable(IPv6NDLinkLayerAddressCriterion.class);
+ assertThatClassIsImmutable(MplsCriterion.class);
+ assertThatClassIsImmutable(IPv6ExthdrFlagsCriterion.class);
+ assertThatClassIsImmutable(LambdaCriterion.class);
+ assertThatClassIsImmutable(OpticalSignalTypeCriterion.class);
+ }
+
+ // PortCriterion class
+
+ /**
+ * Test the matchInPort method.
+ */
+ @Test
+ public void testMatchInPortMethod() {
+ PortNumber p1 = portNumber(1);
+ Criterion matchInPort = Criteria.matchInPort(p1);
+ PortCriterion portCriterion =
+ checkAndConvert(matchInPort,
+ Criterion.Type.IN_PORT,
+ PortCriterion.class);
+ assertThat(portCriterion.port(), is(equalTo(p1)));
+ }
+
+ /**
+ * Test the matchInPhyPort method.
+ */
+ @Test
+ public void testMatchInPhyPortMethod() {
+ PortNumber p1 = portNumber(1);
+ Criterion matchInPhyPort = Criteria.matchInPhyPort(p1);
+ PortCriterion portCriterion =
+ checkAndConvert(matchInPhyPort,
+ Criterion.Type.IN_PHY_PORT,
+ PortCriterion.class);
+ assertThat(portCriterion.port(), is(equalTo(p1)));
+ }
+
+ /**
+ * Test the equals() method of the PortCriterion class.
+ */
+ @Test
+ public void testPortCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchInPort1, sameAsMatchInPort1)
+ .addEqualityGroup(matchInPort2)
+ .testEquals();
+
+ new EqualsTester()
+ .addEqualityGroup(matchInPhyPort1, sameAsMatchInPhyPort1)
+ .addEqualityGroup(matchInPhyPort2)
+ .testEquals();
+ }
+
+ // MetadataCriterion class
+
+ /**
+ * Test the matchMetadata method.
+ */
+ @Test
+ public void testMatchMetadataMethod() {
+ Long metadata = 12L;
+ Criterion matchMetadata = Criteria.matchMetadata(metadata);
+ MetadataCriterion metadataCriterion =
+ checkAndConvert(matchMetadata,
+ Criterion.Type.METADATA,
+ MetadataCriterion.class);
+ assertThat(metadataCriterion.metadata(), is(equalTo(metadata)));
+ }
+
+ /**
+ * Test the equals() method of the MetadataCriterion class.
+ */
+ @Test
+ public void testMetadataCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchMetadata1, sameAsMatchMetadata1)
+ .addEqualityGroup(matchMetadata2)
+ .testEquals();
+ }
+
+ // EthCriterion class
+
+ /**
+ * Test the matchEthDst method.
+ */
+ @Test
+ public void testMatchEthDstMethod() {
+ Criterion matchEthDst = Criteria.matchEthDst(mac1);
+ EthCriterion ethCriterion =
+ checkAndConvert(matchEthDst,
+ Criterion.Type.ETH_DST,
+ EthCriterion.class);
+ assertThat(ethCriterion.mac(), is(equalTo(mac1)));
+ }
+
+ /**
+ * Test the matchEthSrc method.
+ */
+ @Test
+ public void testMatchEthSrcMethod() {
+ Criterion matchEthSrc = Criteria.matchEthSrc(mac1);
+ EthCriterion ethCriterion =
+ checkAndConvert(matchEthSrc,
+ Criterion.Type.ETH_SRC,
+ EthCriterion.class);
+ assertThat(ethCriterion.mac(), is(mac1));
+ }
+
+ /**
+ * Test the equals() method of the EthCriterion class.
+ */
+ @Test
+ public void testEthCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchEth1, sameAsMatchEth1)
+ .addEqualityGroup(matchEth2)
+ .testEquals();
+ }
+
+ // EthTypeCriterion class
+
+ /**
+ * Test the matchEthType method.
+ */
+ @Test
+ public void testMatchEthTypeMethod() {
+ EthType ethType = new EthType(12);
+ Criterion matchEthType = Criteria.matchEthType(new EthType(12));
+ EthTypeCriterion ethTypeCriterion =
+ checkAndConvert(matchEthType,
+ Criterion.Type.ETH_TYPE,
+ EthTypeCriterion.class);
+ assertThat(ethTypeCriterion.ethType(), is(equalTo(ethType)));
+ }
+
+ /**
+ * Test the equals() method of the EthTypeCriterion class.
+ */
+ @Test
+ public void testEthTypeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchEthType1, sameAsMatchEthType1)
+ .addEqualityGroup(matchEthType2)
+ .testEquals();
+ }
+
+ // VlanIdCriterion class
+
+ /**
+ * Test the matchVlanId method.
+ */
+ @Test
+ public void testMatchVlanIdMethod() {
+ Criterion matchVlanId = Criteria.matchVlanId(vlanId1);
+ VlanIdCriterion vlanIdCriterion =
+ checkAndConvert(matchVlanId,
+ Criterion.Type.VLAN_VID,
+ VlanIdCriterion.class);
+ assertThat(vlanIdCriterion.vlanId(), is(equalTo(vlanId1)));
+ }
+
+ /**
+ * Test the equals() method of the VlanIdCriterion class.
+ */
+ @Test
+ public void testVlanIdCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchVlanId1, sameAsMatchVlanId1)
+ .addEqualityGroup(matchVlanId2)
+ .testEquals();
+ }
+
+ // VlanPcpCriterion class
+
+ /**
+ * Test the matchVlanPcp method.
+ */
+ @Test
+ public void testMatchVlanPcpMethod() {
+ Criterion matchVlanPcp = Criteria.matchVlanPcp(vlanPcp1);
+ VlanPcpCriterion vlanPcpCriterion =
+ checkAndConvert(matchVlanPcp,
+ Criterion.Type.VLAN_PCP,
+ VlanPcpCriterion.class);
+ assertThat(vlanPcpCriterion.priority(), is(equalTo(vlanPcp1)));
+ }
+
+ /**
+ * Test the equals() method of the VlanPcpCriterion class.
+ */
+ @Test
+ public void testVlanPcpCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchVlanPcp1, sameAsMatchVlanPcp1)
+ .addEqualityGroup(matchVlanPcp2)
+ .testEquals();
+ }
+
+ // IPDscpCriterion class
+
+ /**
+ * Test the matchIPDscp method.
+ */
+ @Test
+ public void testMatchIPDscpMethod() {
+ Criterion matchIPDscp = Criteria.matchIPDscp(ipDscp1);
+ IPDscpCriterion ipDscpCriterion =
+ checkAndConvert(matchIPDscp,
+ Criterion.Type.IP_DSCP,
+ IPDscpCriterion.class);
+ assertThat(ipDscpCriterion.ipDscp(), is(equalTo(ipDscp1)));
+ }
+
+ /**
+ * Test the equals() method of the IPDscpCriterion class.
+ */
+ @Test
+ public void testIPDscpCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIpDscp1, sameAsMatchIpDscp1)
+ .addEqualityGroup(matchIpDscp2)
+ .testEquals();
+ }
+
+ // IPEcnCriterion class
+
+ /**
+ * Test the matchIPEcn method.
+ */
+ @Test
+ public void testMatchIPEcnMethod() {
+ Criterion matchIPEcn = Criteria.matchIPEcn(ipEcn1);
+ IPEcnCriterion ipEcnCriterion =
+ checkAndConvert(matchIPEcn,
+ Criterion.Type.IP_ECN,
+ IPEcnCriterion.class);
+ assertThat(ipEcnCriterion.ipEcn(), is(equalTo(ipEcn1)));
+ }
+
+ /**
+ * Test the equals() method of the IPEcnCriterion class.
+ */
+ @Test
+ public void testIPEcnCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIpEcn1, sameAsMatchIpEcn1)
+ .addEqualityGroup(matchIpEcn2)
+ .testEquals();
+ }
+
+ // IpProtocolCriterion class
+
+ /**
+ * Test the matchIpProtocol method.
+ */
+ @Test
+ public void testMatchIpProtocolMethod() {
+ Criterion matchIPProtocol = Criteria.matchIPProtocol(protocol1);
+ IPProtocolCriterion ipProtocolCriterion =
+ checkAndConvert(matchIPProtocol,
+ Criterion.Type.IP_PROTO,
+ IPProtocolCriterion.class);
+ assertThat(ipProtocolCriterion.protocol(), is(equalTo(protocol1)));
+ }
+
+ /**
+ * Test the equals() method of the IpProtocolCriterion class.
+ */
+ @Test
+ public void testIpProtocolCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIpProtocol1, sameAsMatchIpProtocol1)
+ .addEqualityGroup(matchIpProtocol2)
+ .testEquals();
+ }
+
+ // IPCriterion class
+
+ /**
+ * Test the matchIPSrc method: IPv4.
+ */
+ @Test
+ public void testMatchIPSrcMethod() {
+ Criterion matchIpSrc = Criteria.matchIPSrc(ip1);
+ IPCriterion ipCriterion =
+ checkAndConvert(matchIpSrc,
+ Criterion.Type.IPV4_SRC,
+ IPCriterion.class);
+ assertThat(ipCriterion.ip(), is(ip1));
+ }
+
+ /**
+ * Test the matchIPDst method: IPv4.
+ */
+ @Test
+ public void testMatchIPDstMethod() {
+ Criterion matchIPDst = Criteria.matchIPDst(ip1);
+ IPCriterion ipCriterion =
+ checkAndConvert(matchIPDst,
+ Criterion.Type.IPV4_DST,
+ IPCriterion.class);
+ assertThat(ipCriterion.ip(), is(equalTo(ip1)));
+ }
+
+ /**
+ * Test the matchIPSrc method: IPv6.
+ */
+ @Test
+ public void testMatchIPv6SrcMethod() {
+ Criterion matchIpv6Src = Criteria.matchIPv6Src(ipv61);
+ IPCriterion ipCriterion =
+ checkAndConvert(matchIpv6Src,
+ Criterion.Type.IPV6_SRC,
+ IPCriterion.class);
+ assertThat(ipCriterion.ip(), is(ipv61));
+ }
+
+ /**
+ * Test the matchIPDst method: IPv6.
+ */
+ @Test
+ public void testMatchIPv6DstMethod() {
+ Criterion matchIPv6Dst = Criteria.matchIPv6Dst(ipv61);
+ IPCriterion ipCriterion =
+ checkAndConvert(matchIPv6Dst,
+ Criterion.Type.IPV6_DST,
+ IPCriterion.class);
+ assertThat(ipCriterion.ip(), is(equalTo(ipv61)));
+ }
+
+ /**
+ * Test the equals() method of the IpCriterion class.
+ */
+ @Test
+ public void testIPCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIp1, sameAsMatchIp1)
+ .addEqualityGroup(matchIp2)
+ .testEquals();
+
+ new EqualsTester()
+ .addEqualityGroup(matchIpv61, sameAsMatchIpv61)
+ .addEqualityGroup(matchIpv62)
+ .testEquals();
+ }
+
+ // TcpPortCriterion class
+
+ /**
+ * Test the matchTcpSrc method.
+ */
+ @Test
+ public void testMatchTcpSrcMethod() {
+ Criterion matchTcpSrc = Criteria.matchTcpSrc(tpPort1);
+ TcpPortCriterion tcpPortCriterion =
+ checkAndConvert(matchTcpSrc,
+ Criterion.Type.TCP_SRC,
+ TcpPortCriterion.class);
+ assertThat(tcpPortCriterion.tcpPort(), is(equalTo(tpPort1)));
+ }
+
+ /**
+ * Test the matchTcpDst method.
+ */
+ @Test
+ public void testMatchTcpDstMethod() {
+ Criterion matchTcpDst = Criteria.matchTcpDst(tpPort1);
+ TcpPortCriterion tcpPortCriterion =
+ checkAndConvert(matchTcpDst,
+ Criterion.Type.TCP_DST,
+ TcpPortCriterion.class);
+ assertThat(tcpPortCriterion.tcpPort(), is(equalTo(tpPort1)));
+ }
+
+ /**
+ * Test the equals() method of the TcpPortCriterion class.
+ */
+ @Test
+ public void testTcpPortCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchTcpPort1, sameAsMatchTcpPort1)
+ .addEqualityGroup(matchTcpPort2)
+ .testEquals();
+ }
+
+ // UdpPortCriterion class
+
+ /**
+ * Test the matchUdpSrc method.
+ */
+ @Test
+ public void testMatchUdpSrcMethod() {
+ Criterion matchUdpSrc = Criteria.matchUdpSrc(tpPort1);
+ UdpPortCriterion udpPortCriterion =
+ checkAndConvert(matchUdpSrc,
+ Criterion.Type.UDP_SRC,
+ UdpPortCriterion.class);
+ assertThat(udpPortCriterion.udpPort(), is(equalTo(tpPort1)));
+ }
+
+ /**
+ * Test the matchUdpDst method.
+ */
+ @Test
+ public void testMatchUdpDstMethod() {
+ Criterion matchUdpDst = Criteria.matchUdpDst(tpPort1);
+ UdpPortCriterion udpPortCriterion =
+ checkAndConvert(matchUdpDst,
+ Criterion.Type.UDP_DST,
+ UdpPortCriterion.class);
+ assertThat(udpPortCriterion.udpPort(), is(equalTo(tpPort1)));
+ }
+
+ /**
+ * Test the equals() method of the UdpPortCriterion class.
+ */
+ @Test
+ public void testUdpPortCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchUdpPort1, sameAsMatchUdpPort1)
+ .addEqualityGroup(matchUdpPort2)
+ .testEquals();
+ }
+
+ // SctpPortCriterion class
+
+ /**
+ * Test the matchSctpSrc method.
+ */
+ @Test
+ public void testMatchSctpSrcMethod() {
+ Criterion matchSctpSrc = Criteria.matchSctpSrc(tpPort1);
+ SctpPortCriterion sctpPortCriterion =
+ checkAndConvert(matchSctpSrc,
+ Criterion.Type.SCTP_SRC,
+ SctpPortCriterion.class);
+ assertThat(sctpPortCriterion.sctpPort(), is(equalTo(tpPort1)));
+ }
+
+ /**
+ * Test the matchSctpDst method.
+ */
+ @Test
+ public void testMatchSctpDstMethod() {
+ Criterion matchSctpDst = Criteria.matchSctpDst(tpPort1);
+ SctpPortCriterion sctpPortCriterion =
+ checkAndConvert(matchSctpDst,
+ Criterion.Type.SCTP_DST,
+ SctpPortCriterion.class);
+ assertThat(sctpPortCriterion.sctpPort(), is(equalTo(tpPort1)));
+ }
+
+ /**
+ * Test the equals() method of the SctpPortCriterion class.
+ */
+ @Test
+ public void testSctpPortCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchSctpPort1, sameAsMatchSctpPort1)
+ .addEqualityGroup(matchSctpPort2)
+ .testEquals();
+ }
+
+ // IcmpTypeCriterion class
+
+ /**
+ * Test the matchIcmpType method.
+ */
+ @Test
+ public void testMatchIcmpTypeMethod() {
+ short icmpType = 12;
+ Criterion matchIcmpType = Criteria.matchIcmpType(icmpType);
+ IcmpTypeCriterion icmpTypeCriterion =
+ checkAndConvert(matchIcmpType,
+ Criterion.Type.ICMPV4_TYPE,
+ IcmpTypeCriterion.class);
+ assertThat(icmpTypeCriterion.icmpType(), is(equalTo(icmpType)));
+ }
+
+ /**
+ * Test the equals() method of the IcmpTypeCriterion class.
+ */
+ @Test
+ public void testIcmpTypeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIcmpType1, sameAsMatchIcmpType1)
+ .addEqualityGroup(matchIcmpType2)
+ .testEquals();
+ }
+
+ // IcmpCodeCriterion class
+
+ /**
+ * Test the matchIcmpCode method.
+ */
+ @Test
+ public void testMatchIcmpCodeMethod() {
+ short icmpCode = 12;
+ Criterion matchIcmpCode = Criteria.matchIcmpCode(icmpCode);
+ IcmpCodeCriterion icmpCodeCriterion =
+ checkAndConvert(matchIcmpCode,
+ Criterion.Type.ICMPV4_CODE,
+ IcmpCodeCriterion.class);
+ assertThat(icmpCodeCriterion.icmpCode(), is(equalTo(icmpCode)));
+ }
+
+ /**
+ * Test the equals() method of the IcmpCodeCriterion class.
+ */
+ @Test
+ public void testIcmpCodeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIcmpCode1, sameAsMatchIcmpCode1)
+ .addEqualityGroup(matchIcmpCode2)
+ .testEquals();
+ }
+
+ // IPv6FlowLabelCriterion class
+
+ /**
+ * Test the matchIPv6FlowLabel method.
+ */
+ @Test
+ public void testMatchIPv6FlowLabelMethod() {
+ int flowLabel = 12;
+ Criterion matchFlowLabel = Criteria.matchIPv6FlowLabel(flowLabel);
+ IPv6FlowLabelCriterion flowLabelCriterion =
+ checkAndConvert(matchFlowLabel,
+ Criterion.Type.IPV6_FLABEL,
+ IPv6FlowLabelCriterion.class);
+ assertThat(flowLabelCriterion.flowLabel(), is(equalTo(flowLabel)));
+ }
+
+ /**
+ * Test the equals() method of the IPv6FlowLabelCriterion class.
+ */
+ @Test
+ public void testIPv6FlowLabelCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchFlowLabel1, sameAsMatchFlowLabel1)
+ .addEqualityGroup(matchFlowLabel2)
+ .testEquals();
+ }
+
+ // Icmpv6TypeCriterion class
+
+ /**
+ * Test the matchIcmpv6Type method.
+ */
+ @Test
+ public void testMatchIcmpv6TypeMethod() {
+ short icmpv6Type = 12;
+ Criterion matchIcmpv6Type = Criteria.matchIcmpv6Type(icmpv6Type);
+ Icmpv6TypeCriterion icmpv6TypeCriterion =
+ checkAndConvert(matchIcmpv6Type,
+ Criterion.Type.ICMPV6_TYPE,
+ Icmpv6TypeCriterion.class);
+ assertThat(icmpv6TypeCriterion.icmpv6Type(), is(equalTo(icmpv6Type)));
+ }
+
+ /**
+ * Test the equals() method of the Icmpv6TypeCriterion class.
+ */
+ @Test
+ public void testIcmpv6TypeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIcmpv6Type1, sameAsMatchIcmpv6Type1)
+ .addEqualityGroup(matchIcmpv6Type2)
+ .testEquals();
+ }
+
+ // Icmpv6CodeCriterion class
+
+ /**
+ * Test the matchIcmpv6Code method.
+ */
+ @Test
+ public void testMatchIcmpv6CodeMethod() {
+ short icmpv6Code = 12;
+ Criterion matchIcmpv6Code = Criteria.matchIcmpv6Code(icmpv6Code);
+ Icmpv6CodeCriterion icmpv6CodeCriterion =
+ checkAndConvert(matchIcmpv6Code,
+ Criterion.Type.ICMPV6_CODE,
+ Icmpv6CodeCriterion.class);
+ assertThat(icmpv6CodeCriterion.icmpv6Code(), is(equalTo(icmpv6Code)));
+ }
+
+ /**
+ * Test the equals() method of the Icmpv6CodeCriterion class.
+ */
+ @Test
+ public void testIcmpv6CodeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIcmpv6Code1, sameAsMatchIcmpv6Code1)
+ .addEqualityGroup(matchIcmpv6Code2)
+ .testEquals();
+ }
+
+ // IPv6NDTargetAddressCriterion class
+
+ /**
+ * Test the matchIPv6NDTargetAddress method.
+ */
+ @Test
+ public void testMatchIPv6NDTargetAddressMethod() {
+ Criterion matchTargetAddress =
+ Criteria.matchIPv6NDTargetAddress(ip6TargetAddress1);
+ IPv6NDTargetAddressCriterion targetAddressCriterion =
+ checkAndConvert(matchTargetAddress,
+ Criterion.Type.IPV6_ND_TARGET,
+ IPv6NDTargetAddressCriterion.class);
+ assertThat(targetAddressCriterion.targetAddress(),
+ is(ip6TargetAddress1));
+ }
+
+ /**
+ * Test the equals() method of the IPv6NDTargetAddressCriterion class.
+ */
+ @Test
+ public void testIPv6NDTargetAddressCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIpv6TargetAddr1,
+ sameAsMatchIpv6TargetAddr1)
+ .addEqualityGroup(matchIpv6TargetAddr2)
+ .testEquals();
+ }
+
+ // IPv6NDLinkLayerAddressCriterion class
+
+ /**
+ * Test the matchIPv6NDSourceLinkLayerAddress method.
+ */
+ @Test
+ public void testMatchIPv6NDSourceLinkLayerAddressMethod() {
+ Criterion matchSrcLlAddr =
+ Criteria.matchIPv6NDSourceLinkLayerAddress(llMac1);
+ IPv6NDLinkLayerAddressCriterion srcLlCriterion =
+ checkAndConvert(matchSrcLlAddr,
+ Criterion.Type.IPV6_ND_SLL,
+ IPv6NDLinkLayerAddressCriterion.class);
+ assertThat(srcLlCriterion.mac(), is(equalTo(llMac1)));
+ }
+
+ /**
+ * Test the matchIPv6NDTargetLinkLayerAddress method.
+ */
+ @Test
+ public void testMatchIPv6NDTargetLinkLayerAddressMethod() {
+ Criterion matchTargetLlAddr =
+ Criteria.matchIPv6NDTargetLinkLayerAddress(llMac1);
+ IPv6NDLinkLayerAddressCriterion targetLlCriterion =
+ checkAndConvert(matchTargetLlAddr,
+ Criterion.Type.IPV6_ND_TLL,
+ IPv6NDLinkLayerAddressCriterion.class);
+ assertThat(targetLlCriterion.mac(), is(equalTo(llMac1)));
+ }
+
+ /**
+ * Test the equals() method of the IPv6NDLinkLayerAddressCriterion class.
+ */
+ @Test
+ public void testIPv6NDLinkLayerAddressCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchSrcLlAddr1, sameAsMatchSrcLlAddr1)
+ .addEqualityGroup(matchSrcLlAddr2)
+ .testEquals();
+
+ new EqualsTester()
+ .addEqualityGroup(matchTargetLlAddr1, sameAsMatchTargetLlAddr1)
+ .addEqualityGroup(matchTargetLlAddr2)
+ .testEquals();
+ }
+
+ // MplsCriterion class
+
+ /**
+ * Test the matchMplsLabel method.
+ */
+ @Test
+ public void testMatchMplsLabelMethod() {
+ Criterion matchMplsLabel = Criteria.matchMplsLabel(mpls1);
+ MplsCriterion mplsCriterion =
+ checkAndConvert(matchMplsLabel,
+ Criterion.Type.MPLS_LABEL,
+ MplsCriterion.class);
+ assertThat(mplsCriterion.label(), is(equalTo(mpls1)));
+ }
+
+ /**
+ * Test the equals() method of the MplsCriterion class.
+ */
+ @Test
+ public void testMplsCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchMpls1, sameAsMatchMpls1)
+ .addEqualityGroup(matchMpls2)
+ .testEquals();
+ }
+
+ // TunnelIdCriterion class
+
+ /**
+ * Test the matchTunnelId method.
+ */
+ @Test
+ public void testMatchTunnelIdMethod() {
+ Criterion matchTunnelId = Criteria.matchTunnelId(tunnelId1);
+ TunnelIdCriterion tunnelIdCriterion =
+ checkAndConvert(matchTunnelId,
+ Criterion.Type.TUNNEL_ID,
+ TunnelIdCriterion.class);
+ assertThat(tunnelIdCriterion.tunnelId(), is(equalTo(tunnelId1)));
+
+ }
+
+ /**
+ * Test the equals() method of the TunnelIdCriterion class.
+ */
+ @Test
+ public void testTunnelIdCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchTunnelId1, sameAsMatchTunnelId1)
+ .addEqualityGroup(matchTunnelId2)
+ .testEquals();
+ }
+
+ // IPv6ExthdrFlagsCriterion class
+
+ /**
+ * Test the matchIPv6ExthdrFlags method.
+ */
+ @Test
+ public void testMatchIPv6ExthdrFlagsMethod() {
+ Criterion matchExthdrFlags =
+ Criteria.matchIPv6ExthdrFlags(ipv6ExthdrFlags1);
+ IPv6ExthdrFlagsCriterion exthdrFlagsCriterion =
+ checkAndConvert(matchExthdrFlags,
+ Criterion.Type.IPV6_EXTHDR,
+ IPv6ExthdrFlagsCriterion.class);
+ assertThat(exthdrFlagsCriterion.exthdrFlags(),
+ is(equalTo(ipv6ExthdrFlags1)));
+ }
+
+ /**
+ * Test the equals() method of the IPv6ExthdrFlagsCriterion class.
+ */
+ @Test
+ public void testIPv6ExthdrFlagsCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIpv6ExthdrFlags1,
+ sameAsMatchIpv6ExthdrFlags1)
+ .addEqualityGroup(matchIpv6ExthdrFlags2)
+ .testEquals();
+ }
+
+ // LambdaCriterion class
+
+ /**
+ * Test the matchLambda method.
+ */
+ @Test
+ public void testMatchLambdaMethod() {
+ Criterion matchLambda = Criteria.matchLambda(lambda1);
+ LambdaCriterion lambdaCriterion =
+ checkAndConvert(matchLambda,
+ Criterion.Type.OCH_SIGID,
+ LambdaCriterion.class);
+ assertThat(lambdaCriterion.lambda(), is(equalTo(lambda1)));
+ }
+
+ /**
+ * Test the equals() method of the LambdaCriterion class.
+ */
+ @Test
+ public void testLambdaCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchLambda1, sameAsMatchLambda1)
+ .addEqualityGroup(matchLambda2)
+ .testEquals();
+ }
+
+ @Test
+ public void testIndexedLambdaCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchIndexedLambda1, sameAsMatchIndexedLambda1)
+ .addEqualityGroup(matchIndexedLambda2)
+ .testEquals();
+ }
+
+ @Test
+ public void testOchSignalCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchOchSignal1, sameAsMatchOchSignal1)
+ .addEqualityGroup(matchOchSignal2)
+ .testEquals();
+ }
+
+ /**
+ * Test the equals() method of the OchSignalTypeCriterion class.
+ */
+ @Test
+ public void testOchSignalTypeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchOchSignalType1, sameAsMatchOchSignalType1)
+ .addEqualityGroup(matchOchSignalType2)
+ .testEquals();
+ }
+
+ // OpticalSignalTypeCriterion class
+
+ /**
+ * Test the matchOpticalSignalType method.
+ */
+ @Test
+ public void testMatchOpticalSignalTypeMethod() {
+ Criterion matchLambda = Criteria.matchOpticalSignalType(signalLambda1);
+ OpticalSignalTypeCriterion opticalSignalTypeCriterion =
+ checkAndConvert(matchLambda,
+ Criterion.Type.OCH_SIGTYPE,
+ OpticalSignalTypeCriterion.class);
+ assertThat(opticalSignalTypeCriterion.signalType(), is(equalTo(signalLambda1)));
+ }
+
+ /**
+ * Test the equals() method of the OpticalSignalTypeCriterion class.
+ */
+ @Test
+ public void testOpticalSignalTypeCriterionEquals() {
+ new EqualsTester()
+ .addEqualityGroup(matchSignalLambda1, sameAsMatchSignalLambda1)
+ .addEqualityGroup(matchSignalLambda2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
new file mode 100644
index 00000000..410349b5
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
@@ -0,0 +1,725 @@
+/*
+ * 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.instructions;
+
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.PortNumber;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onlab.junit.UtilityClassChecker.assertThatClassIsUtility;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Unit tests for the Instructions class.
+ */
+public class InstructionsTest {
+
+ /**
+ * Checks that a Criterion object has the proper type, and then converts
+ * it to the proper type.
+ *
+ * @param instruction Instruction object to convert
+ * @param type Enumerated type value for the Criterion class
+ * @param clazz Desired Criterion class
+ * @param <T> The type the caller wants returned
+ * @return converted object
+ */
+ @SuppressWarnings("unchecked")
+ private <T> T checkAndConvert(Instruction instruction, Instruction.Type type, Class clazz) {
+ assertThat(instruction, is(notNullValue()));
+ assertThat(instruction.type(), is(equalTo(type)));
+ assertThat(instruction, instanceOf(clazz));
+ return (T) instruction;
+ }
+
+ /**
+ * Checks the equals() and toString() methods of a Criterion class.
+ *
+ * @param c1 first object to compare
+ * @param c1match object that should be equal to the first
+ * @param c2 object that should be not equal to the first
+ * @param <T> type of the arguments
+ */
+ private <T extends Instruction> void checkEqualsAndToString(T c1, T c1match,
+ T c2) {
+
+ new EqualsTester()
+ .addEqualityGroup(c1, c1match)
+ .addEqualityGroup(c2)
+ .testEquals();
+ }
+
+ /**
+ * Checks that Instructions is a proper utility class.
+ */
+ @Test
+ public void testInstructionsUtilityClass() {
+ assertThatClassIsUtility(Instructions.class);
+ }
+
+ /**
+ * Checks that the Instruction class implementations are immutable.
+ */
+ @Test
+ public void testImmutabilityOfInstructions() {
+ assertThatClassIsImmutable(Instructions.DropInstruction.class);
+ assertThatClassIsImmutable(Instructions.OutputInstruction.class);
+ assertThatClassIsImmutable(L0ModificationInstruction.ModLambdaInstruction.class);
+ assertThatClassIsImmutable(L0ModificationInstruction.ModOchSignalInstruction.class);
+ assertThatClassIsImmutable(L2ModificationInstruction.ModEtherInstruction.class);
+ assertThatClassIsImmutable(L2ModificationInstruction.ModVlanIdInstruction.class);
+ assertThatClassIsImmutable(L2ModificationInstruction.ModVlanPcpInstruction.class);
+ assertThatClassIsImmutable(L3ModificationInstruction.ModIPInstruction.class);
+ assertThatClassIsImmutable(L3ModificationInstruction.ModIPv6FlowLabelInstruction.class);
+ assertThatClassIsImmutable(L2ModificationInstruction.ModMplsLabelInstruction.class);
+ assertThatClassIsImmutable(L2ModificationInstruction.PushHeaderInstructions.class);
+ }
+
+ // DropInstruction
+
+ private final Instructions.DropInstruction drop1 = Instructions.createDrop();
+ private final Instructions.DropInstruction drop2 = Instructions.createDrop();
+
+ /**
+ * Test the createDrop method.
+ */
+ @Test
+ public void testCreateDropMethod() {
+ Instructions.DropInstruction instruction = Instructions.createDrop();
+ checkAndConvert(instruction,
+ Instruction.Type.DROP,
+ Instructions.DropInstruction.class);
+ }
+
+ /**
+ * Test the equals() method of the DropInstruction class.
+ */
+
+ @Test
+ public void testDropInstructionEquals() throws Exception {
+ assertThat(drop1, is(equalTo(drop2)));
+ }
+
+ /**
+ * Test the hashCode() method of the DropInstruction class.
+ */
+
+ @Test
+ public void testDropInstructionHashCode() {
+ assertThat(drop1.hashCode(), is(equalTo(drop2.hashCode())));
+ }
+
+ // OutputInstruction
+
+ private final PortNumber port1 = portNumber(1);
+ private final PortNumber port2 = portNumber(2);
+ private final Instructions.OutputInstruction output1 = Instructions.createOutput(port1);
+ private final Instructions.OutputInstruction sameAsOutput1 = Instructions.createOutput(port1);
+ private final Instructions.OutputInstruction output2 = Instructions.createOutput(port2);
+
+ /**
+ * Test the createOutput method.
+ */
+ @Test
+ public void testCreateOutputMethod() {
+ final Instruction instruction = Instructions.createOutput(port2);
+ final Instructions.OutputInstruction outputInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.OUTPUT,
+ Instructions.OutputInstruction.class);
+ assertThat(outputInstruction.port(), is(equalTo(port2)));
+ }
+
+
+ /**
+ * Test the equals() method of the OutputInstruction class.
+ */
+
+ @Test
+ public void testOutputInstructionEquals() throws Exception {
+ checkEqualsAndToString(output1, sameAsOutput1, output2);
+ }
+
+ /**
+ * Test the hashCode() method of the OutputInstruction class.
+ */
+
+ @Test
+ public void testOutputInstructionHashCode() {
+ assertThat(output1.hashCode(), is(equalTo(sameAsOutput1.hashCode())));
+ assertThat(output1.hashCode(), is(not(equalTo(output2.hashCode()))));
+ }
+
+ // ModLambdaInstruction
+
+ private final IndexedLambda lambda1 = new IndexedLambda(1);
+ private final IndexedLambda lambda2 = new IndexedLambda(2);
+ private final Instruction lambdaInstruction1 = Instructions.modL0Lambda(lambda1);
+ private final Instruction sameAsLambdaInstruction1 = Instructions.modL0Lambda(lambda1);
+ private final Instruction lambdaInstruction2 = Instructions.modL0Lambda(lambda2);
+
+ /**
+ * Test the modL0Lambda method.
+ */
+ @Test
+ public void testCreateLambdaMethod() {
+ final Instruction instruction = Instructions.modL0Lambda(lambda1);
+ final L0ModificationInstruction.ModLambdaInstruction lambdaInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L0MODIFICATION,
+ L0ModificationInstruction.ModLambdaInstruction.class);
+ assertThat(lambdaInstruction.lambda(), is(equalTo((short) lambda1.index())));
+ }
+
+ /**
+ * Test the equals() method of the ModLambdaInstruction class.
+ */
+
+ @Test
+ public void testModLambdaInstructionEquals() throws Exception {
+ checkEqualsAndToString(lambdaInstruction1,
+ sameAsLambdaInstruction1,
+ lambdaInstruction2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModLambdaInstruction class.
+ */
+
+ @Test
+ public void testModLambdaInstructionHashCode() {
+ assertThat(lambdaInstruction1.hashCode(),
+ is(equalTo(sameAsLambdaInstruction1.hashCode())));
+ assertThat(lambdaInstruction1.hashCode(),
+ is(not(equalTo(lambdaInstruction2.hashCode()))));
+ }
+
+ private final Lambda och1 = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8);
+ private final Lambda och2 = Lambda.ochSignal(GridType.CWDM, ChannelSpacing.CHL_100GHZ, 4, 8);
+ private final Instruction ochInstruction1 = Instructions.modL0Lambda(och1);
+ private final Instruction sameAsOchInstruction1 = Instructions.modL0Lambda(och1);
+ private final Instruction ochInstruction2 = Instructions.modL0Lambda(och2);
+
+ /**
+ * Test the modL0Lambda().
+ */
+ @Test
+ public void testModL0LambdaMethod() {
+ Instruction instruction = Instructions.modL0Lambda(och1);
+ L0ModificationInstruction.ModOchSignalInstruction ochInstruction =
+ checkAndConvert(instruction, Instruction.Type.L0MODIFICATION,
+ L0ModificationInstruction.ModOchSignalInstruction.class);
+ assertThat(ochInstruction.lambda(), is(och1));
+ }
+
+ /**
+ * Test the equals() method of the ModOchSignalInstruction class.
+ */
+ @Test
+ public void testModOchSignalInstructionEquals() {
+ checkEqualsAndToString(ochInstruction1, sameAsOchInstruction1, ochInstruction2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModOchSignalInstruction class.
+ */
+ @Test
+ public void testModOchSignalInstructionHashCode() {
+ assertThat(ochInstruction1.hashCode(), is(sameAsOchInstruction1.hashCode()));
+ assertThat(ochInstruction1.hashCode(), is(not(ochInstruction2.hashCode())));
+ }
+
+ // ModEtherInstruction
+
+ private static final String MAC1 = "00:00:00:00:00:01";
+ private static final String MAC2 = "00:00:00:00:00:02";
+ private final MacAddress mac1 = MacAddress.valueOf(MAC1);
+ private final MacAddress mac2 = MacAddress.valueOf(MAC2);
+ private final Instruction modEtherInstruction1 = Instructions.modL2Src(mac1);
+ private final Instruction sameAsModEtherInstruction1 = Instructions.modL2Src(mac1);
+ private final Instruction modEtherInstruction2 = Instructions.modL2Src(mac2);
+
+ /**
+ * Test the modL2Src method.
+ */
+ @Test
+ public void testModL2SrcMethod() {
+ final Instruction instruction = Instructions.modL2Src(mac1);
+ final L2ModificationInstruction.ModEtherInstruction modEtherInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.ModEtherInstruction.class);
+ assertThat(modEtherInstruction.mac(), is(equalTo(mac1)));
+ assertThat(modEtherInstruction.subtype(),
+ is(equalTo(L2ModificationInstruction.L2SubType.ETH_SRC)));
+ }
+
+ /**
+ * Test the modL2Dst method.
+ */
+ @Test
+ public void testModL2DstMethod() {
+ final Instruction instruction = Instructions.modL2Dst(mac1);
+ final L2ModificationInstruction.ModEtherInstruction modEtherInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.ModEtherInstruction.class);
+ assertThat(modEtherInstruction.mac(), is(equalTo(mac1)));
+ assertThat(modEtherInstruction.subtype(),
+ is(equalTo(L2ModificationInstruction.L2SubType.ETH_DST)));
+ }
+
+ /**
+ * Test the equals() method of the ModEtherInstruction class.
+ */
+
+ @Test
+ public void testModEtherInstructionEquals() throws Exception {
+ checkEqualsAndToString(modEtherInstruction1,
+ sameAsModEtherInstruction1,
+ modEtherInstruction2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModEtherInstruction class.
+ */
+
+ @Test
+ public void testModEtherInstructionHashCode() {
+ assertThat(modEtherInstruction1.hashCode(),
+ is(equalTo(sameAsModEtherInstruction1.hashCode())));
+ assertThat(modEtherInstruction1.hashCode(),
+ is(not(equalTo(modEtherInstruction2.hashCode()))));
+ }
+
+
+ // ModVlanIdInstruction
+
+ private final short vlan1 = 1;
+ private final short vlan2 = 2;
+ private final VlanId vlanId1 = VlanId.vlanId(vlan1);
+ private final VlanId vlanId2 = VlanId.vlanId(vlan2);
+ private final Instruction modVlanId1 = Instructions.modVlanId(vlanId1);
+ private final Instruction sameAsModVlanId1 = Instructions.modVlanId(vlanId1);
+ private final Instruction modVlanId2 = Instructions.modVlanId(vlanId2);
+
+ /**
+ * Test the modVlanId method.
+ */
+ @Test
+ public void testModVlanIdMethod() {
+ final Instruction instruction = Instructions.modVlanId(vlanId1);
+ final L2ModificationInstruction.ModVlanIdInstruction modEtherInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.ModVlanIdInstruction.class);
+ assertThat(modEtherInstruction.vlanId(), is(equalTo(vlanId1)));
+ assertThat(modEtherInstruction.subtype(),
+ is(equalTo(L2ModificationInstruction.L2SubType.VLAN_ID)));
+ }
+
+ /**
+ * Test the equals() method of the ModVlanIdInstruction class.
+ */
+
+ @Test
+ public void testModVlanIdInstructionEquals() throws Exception {
+ checkEqualsAndToString(modVlanId1,
+ sameAsModVlanId1,
+ modVlanId2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModEtherInstruction class.
+ */
+
+ @Test
+ public void testModVlanIdInstructionHashCode() {
+ assertThat(modVlanId1.hashCode(),
+ is(equalTo(sameAsModVlanId1.hashCode())));
+ assertThat(modVlanId1.hashCode(),
+ is(not(equalTo(modVlanId2.hashCode()))));
+ }
+
+
+ // ModVlanPcpInstruction
+
+ private final byte vlanPcp1 = 1;
+ private final byte vlanPcp2 = 2;
+ private final Instruction modVlanPcp1 = Instructions.modVlanPcp(vlanPcp1);
+ private final Instruction sameAsModVlanPcp1 = Instructions.modVlanPcp(vlanPcp1);
+ private final Instruction modVlanPcp2 = Instructions.modVlanPcp(vlanPcp2);
+
+ /**
+ * Test the modVlanPcp method.
+ */
+ @Test
+ public void testModVlanPcpMethod() {
+ final Instruction instruction = Instructions.modVlanPcp(vlanPcp1);
+ final L2ModificationInstruction.ModVlanPcpInstruction modEtherInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.ModVlanPcpInstruction.class);
+ assertThat(modEtherInstruction.vlanPcp(), is(equalTo(vlanPcp1)));
+ assertThat(modEtherInstruction.subtype(),
+ is(equalTo(L2ModificationInstruction.L2SubType.VLAN_PCP)));
+ }
+
+ /**
+ * Test the equals() method of the ModVlanPcpInstruction class.
+ */
+
+ @Test
+ public void testModVlanPcpInstructionEquals() throws Exception {
+ checkEqualsAndToString(modVlanPcp1,
+ sameAsModVlanPcp1,
+ modVlanPcp2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModEtherInstruction class.
+ */
+
+ @Test
+ public void testModVlanPcpInstructionHashCode() {
+ assertThat(modVlanPcp1.hashCode(),
+ is(equalTo(sameAsModVlanPcp1.hashCode())));
+ assertThat(modVlanPcp1.hashCode(),
+ is(not(equalTo(modVlanPcp2.hashCode()))));
+ }
+
+ // ModIPInstruction
+
+ private static final String IP41 = "1.2.3.4";
+ private static final String IP42 = "5.6.7.8";
+ private IpAddress ip41 = IpAddress.valueOf(IP41);
+ private IpAddress ip42 = IpAddress.valueOf(IP42);
+ private final Instruction modIPInstruction1 = Instructions.modL3Src(ip41);
+ private final Instruction sameAsModIPInstruction1 = Instructions.modL3Src(ip41);
+ private final Instruction modIPInstruction2 = Instructions.modL3Src(ip42);
+
+ private static final String IP61 = "1111::2222";
+ private static final String IP62 = "3333::4444";
+ private IpAddress ip61 = IpAddress.valueOf(IP61);
+ private IpAddress ip62 = IpAddress.valueOf(IP62);
+ private final Instruction modIPv6Instruction1 =
+ Instructions.modL3IPv6Src(ip61);
+ private final Instruction sameAsModIPv6Instruction1 =
+ Instructions.modL3IPv6Src(ip61);
+ private final Instruction modIPv6Instruction2 =
+ Instructions.modL3IPv6Src(ip62);
+
+ /**
+ * Test the modL3Src method.
+ */
+ @Test
+ public void testModL3SrcMethod() {
+ final Instruction instruction = Instructions.modL3Src(ip41);
+ final L3ModificationInstruction.ModIPInstruction modIPInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.ModIPInstruction.class);
+ assertThat(modIPInstruction.ip(), is(equalTo(ip41)));
+ assertThat(modIPInstruction.subtype(),
+ is(equalTo(L3ModificationInstruction.L3SubType.IPV4_SRC)));
+ }
+
+ /**
+ * Test the modL3Dst method.
+ */
+ @Test
+ public void testModL3DstMethod() {
+ final Instruction instruction = Instructions.modL3Dst(ip41);
+ final L3ModificationInstruction.ModIPInstruction modIPInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.ModIPInstruction.class);
+ assertThat(modIPInstruction.ip(), is(equalTo(ip41)));
+ assertThat(modIPInstruction.subtype(),
+ is(equalTo(L3ModificationInstruction.L3SubType.IPV4_DST)));
+ }
+
+ /**
+ * Test the modL3IPv6Src method.
+ */
+ @Test
+ public void testModL3IPv6SrcMethod() {
+ final Instruction instruction = Instructions.modL3IPv6Src(ip61);
+ final L3ModificationInstruction.ModIPInstruction modIPInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.ModIPInstruction.class);
+ assertThat(modIPInstruction.ip(), is(equalTo(ip61)));
+ assertThat(modIPInstruction.subtype(),
+ is(equalTo(L3ModificationInstruction.L3SubType.IPV6_SRC)));
+ }
+
+ /**
+ * Test the modL3IPv6Dst method.
+ */
+ @Test
+ public void testModL3IPv6DstMethod() {
+ final Instruction instruction = Instructions.modL3IPv6Dst(ip61);
+ final L3ModificationInstruction.ModIPInstruction modIPInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.ModIPInstruction.class);
+ assertThat(modIPInstruction.ip(), is(equalTo(ip61)));
+ assertThat(modIPInstruction.subtype(),
+ is(equalTo(L3ModificationInstruction.L3SubType.IPV6_DST)));
+ }
+
+ /**
+ * Test the equals() method of the ModIPInstruction class.
+ */
+ @Test
+ public void testModIPInstructionEquals() throws Exception {
+ checkEqualsAndToString(modIPInstruction1,
+ sameAsModIPInstruction1,
+ modIPInstruction2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModIPInstruction class.
+ */
+ @Test
+ public void testModIPInstructionHashCode() {
+ assertThat(modIPInstruction1.hashCode(),
+ is(equalTo(sameAsModIPInstruction1.hashCode())));
+ assertThat(modIPInstruction1.hashCode(),
+ is(not(equalTo(modIPInstruction2.hashCode()))));
+ }
+
+ private final int flowLabel1 = 0x11111;
+ private final int flowLabel2 = 0x22222;
+ private final Instruction modIPv6FlowLabelInstruction1 =
+ Instructions.modL3IPv6FlowLabel(flowLabel1);
+ private final Instruction sameAsModIPv6FlowLabelInstruction1 =
+ Instructions.modL3IPv6FlowLabel(flowLabel1);
+ private final Instruction modIPv6FlowLabelInstruction2 =
+ Instructions.modL3IPv6FlowLabel(flowLabel2);
+
+ /**
+ * Test the modL3IPv6FlowLabel method.
+ */
+ @Test
+ public void testModL3IPv6FlowLabelMethod() {
+ final Instruction instruction =
+ Instructions.modL3IPv6FlowLabel(flowLabel1);
+ final L3ModificationInstruction.ModIPv6FlowLabelInstruction
+ modIPv6FlowLabelInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.ModIPv6FlowLabelInstruction.class);
+ assertThat(modIPv6FlowLabelInstruction.flowLabel(),
+ is(equalTo(flowLabel1)));
+ assertThat(modIPv6FlowLabelInstruction.subtype(),
+ is(equalTo(L3ModificationInstruction.L3SubType.IPV6_FLABEL)));
+ }
+
+ /**
+ * Test the equals() method of the ModIPv6FlowLabelInstruction class.
+ */
+ @Test
+ public void testModIPv6FlowLabelInstructionEquals() throws Exception {
+ checkEqualsAndToString(modIPv6FlowLabelInstruction1,
+ sameAsModIPv6FlowLabelInstruction1,
+ modIPv6FlowLabelInstruction2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModIPv6FlowLabelInstruction class.
+ */
+ @Test
+ public void testModIPv6FlowLabelInstructionHashCode() {
+ assertThat(modIPv6FlowLabelInstruction1.hashCode(),
+ is(equalTo(sameAsModIPv6FlowLabelInstruction1.hashCode())));
+ assertThat(modIPv6FlowLabelInstruction1.hashCode(),
+ is(not(equalTo(modIPv6FlowLabelInstruction2.hashCode()))));
+ }
+
+ private Instruction modMplsLabelInstruction1 = Instructions.modMplsLabel(MplsLabel.mplsLabel(1));
+ private Instruction sameAsModMplsLabelInstruction1 = Instructions.modMplsLabel(MplsLabel.mplsLabel(1));
+ private Instruction modMplsLabelInstruction2 = Instructions.modMplsLabel(MplsLabel.mplsLabel(2));
+
+ /**
+ * Test the modMplsLabel method.
+ */
+ @Test
+ public void testModMplsMethod() {
+ final MplsLabel mplsLabel = MplsLabel.mplsLabel(33);
+ final Instruction instruction = Instructions.modMplsLabel(mplsLabel);
+ final L2ModificationInstruction.ModMplsLabelInstruction modMplsLabelInstruction =
+ checkAndConvert(instruction,
+ Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.ModMplsLabelInstruction.class);
+ assertThat(modMplsLabelInstruction.mplsLabel(), is(equalTo(mplsLabel)));
+ assertThat(modMplsLabelInstruction.subtype(),
+ is(equalTo(L2ModificationInstruction.L2SubType.MPLS_LABEL)));
+ }
+
+ /**
+ * Test the equals(), hashCode and toString() methods of the
+ * ModMplsLabelInstruction class.
+ */
+ @Test
+ public void testModMplsLabelInstructionEquals() throws Exception {
+ checkEqualsAndToString(modMplsLabelInstruction1,
+ sameAsModMplsLabelInstruction1,
+ modMplsLabelInstruction2);
+ }
+
+ // ModTunnelIdInstruction
+
+ private final long tunnelId1 = 1L;
+ private final long tunnelId2 = 2L;
+ private final Instruction modTunnelId1 = Instructions.modTunnelId(tunnelId1);
+ private final Instruction sameAsModTunnelId1 = Instructions.modTunnelId(tunnelId1);
+ private final Instruction modTunnelId2 = Instructions.modTunnelId(tunnelId2);
+
+ /**
+ * Test the modTunnelId method.
+ */
+ @Test
+ public void testModTunnelIdMethod() {
+ final Instruction instruction = Instructions.modTunnelId(tunnelId1);
+ final L2ModificationInstruction.ModTunnelIdInstruction modTunnelIdInstruction =
+ checkAndConvert(instruction, Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.ModTunnelIdInstruction.class);
+ assertThat(modTunnelIdInstruction.tunnelId(), is(equalTo(tunnelId1)));
+ assertThat(modTunnelIdInstruction.subtype(),
+ is(equalTo(L2ModificationInstruction.L2SubType.TUNNEL_ID)));
+ }
+
+ /***
+ * Test the equals() method of the ModTunnelIdInstruction class.
+ */
+ @Test
+ public void testModTunnelIdInstructionEquals() throws Exception {
+ checkEqualsAndToString(modTunnelId1, sameAsModTunnelId1, modTunnelId2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModTunnelIdInstruction class.
+ */
+ @Test
+ public void testModTunnelIdInstructionHashCode() {
+ assertThat(modTunnelId1.hashCode(), is(equalTo(sameAsModTunnelId1.hashCode())));
+ assertThat(modTunnelId1.hashCode(), is(not(equalTo(modTunnelId2.hashCode()))));
+ }
+
+ // ModTransportPortInstruction
+
+ private final TpPort tpPort1 = TpPort.tpPort(1);
+ private final TpPort tpPort2 = TpPort.tpPort(2);
+ private final Instruction modTransportPortInstruction1 = Instructions.modTcpSrc(tpPort1);
+ private final Instruction sameAsModTransportPortInstruction1 = Instructions.modTcpSrc(tpPort1);
+ private final Instruction modTransportPortInstruction2 = Instructions.modTcpSrc(tpPort2);
+
+ /**
+ * Test the modTcpSrc() method.
+ */
+ @Test
+ public void testModTcpSrcMethod() {
+ final Instruction instruction = Instructions.modTcpSrc(tpPort1);
+ final L4ModificationInstruction.ModTransportPortInstruction modTransportPortInstruction =
+ checkAndConvert(instruction, Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.ModTransportPortInstruction.class);
+ assertThat(modTransportPortInstruction.port(), is(equalTo(tpPort1)));
+ assertThat(modTransportPortInstruction.subtype(),
+ is(equalTo(L4ModificationInstruction.L4SubType.TCP_SRC)));
+ }
+
+ /**
+ * Test the modTcpDst() method.
+ */
+ @Test
+ public void testModTcpDstMethod() {
+ final Instruction instruction = Instructions.modTcpDst(tpPort1);
+ final L4ModificationInstruction.ModTransportPortInstruction modTransportPortInstruction =
+ checkAndConvert(instruction, Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.ModTransportPortInstruction.class);
+ assertThat(modTransportPortInstruction.port(), is(equalTo(tpPort1)));
+ assertThat(modTransportPortInstruction.subtype(),
+ is(equalTo(L4ModificationInstruction.L4SubType.TCP_DST)));
+ }
+
+ /**
+ * Test the modUdpSrc() method.
+ */
+ @Test
+ public void testModUdpSrcMethod() {
+ final Instruction instruction = Instructions.modUdpSrc(tpPort1);
+ final L4ModificationInstruction.ModTransportPortInstruction modTransportPortInstruction =
+ checkAndConvert(instruction, Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.ModTransportPortInstruction.class);
+ assertThat(modTransportPortInstruction.port(), is(equalTo(tpPort1)));
+ assertThat(modTransportPortInstruction.subtype(),
+ is(equalTo(L4ModificationInstruction.L4SubType.UDP_SRC)));
+ }
+
+ /**
+ * Test the modUdpDst() method.
+ */
+ @Test
+ public void testModUdpDstMethod() {
+ final Instruction instruction = Instructions.modUdpDst(tpPort1);
+ final L4ModificationInstruction.ModTransportPortInstruction modTransportPortInstruction =
+ checkAndConvert(instruction, Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.ModTransportPortInstruction.class);
+ assertThat(modTransportPortInstruction.port(), is(equalTo(tpPort1)));
+ assertThat(modTransportPortInstruction.subtype(),
+ is(equalTo(L4ModificationInstruction.L4SubType.UDP_DST)));
+ }
+
+ /**
+ * Test the equals() method of the ModTransportPortInstruction class.
+ */
+ @Test
+ public void testModTransportPortInstructionEquals() throws Exception {
+ checkEqualsAndToString(modTransportPortInstruction1,
+ sameAsModTransportPortInstruction1,
+ modTransportPortInstruction2);
+ }
+
+ /**
+ * Test the hashCode() method of the ModTransportPortInstruction class.
+ */
+ @Test
+ public void testModTransportPortInstructionHashCode() {
+ assertThat(modTransportPortInstruction1.hashCode(),
+ is(equalTo(sameAsModTransportPortInstruction1.hashCode())));
+ assertThat(modTransportPortInstruction1.hashCode(),
+ is(not(equalTo(modTransportPortInstruction2.hashCode()))));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/flowobjective/ObjectiveTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flowobjective/ObjectiveTest.java
new file mode 100644
index 00000000..850582b0
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/flowobjective/ObjectiveTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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;
+
+import org.junit.Test;
+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.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.not;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.flowobjective.FilteringObjective.Type.DENY;
+import static org.onosproject.net.flowobjective.ForwardingObjective.Flag.SPECIFIC;
+import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
+import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
+import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
+
+/**
+ * Unit tests for forwarding objective class.
+ */
+public class ObjectiveTest {
+
+ private final TrafficTreatment treatment =
+ DefaultTrafficTreatment.emptyTreatment();
+ private final TrafficSelector selector =
+ DefaultTrafficSelector.emptySelector();
+ private final Criterion criterion = Criteria.dummy();
+ private final Criterion key = Criteria.dummy();
+
+ /**
+ * Mock objective context.
+ */
+ private static class MockObjectiveContext implements ObjectiveContext {
+ @Override
+ public void onSuccess(Objective objective) {
+ // stub
+ }
+
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ // stub
+ }
+ }
+
+ /**
+ * Checks immutability of objective classes.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(DefaultFilteringObjective.class);
+ assertThatClassIsImmutable(DefaultForwardingObjective.class);
+ assertThatClassIsImmutable(DefaultNextObjective.class);
+ }
+
+ // Forwarding Objectives
+
+ /**
+ * Makes a forwarding objective builder with a set of default values.
+ *
+ * @return forwarding objective builder
+ */
+ private ForwardingObjective.Builder baseForwardingBuilder() {
+ return DefaultForwardingObjective.builder()
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withFlag(SPECIFIC)
+ .fromApp(APP_ID)
+ .withPriority(22)
+ .makeTemporary(5)
+ .nextStep(33);
+ }
+
+ /**
+ * Checks the default values of a forwarding objective object.
+ *
+ * @param objective forwarding objective to check
+ */
+ private void checkForwardingBase(ForwardingObjective objective,
+ Objective.Operation op,
+ ObjectiveContext expectedContext) {
+ assertThat(objective.permanent(), is(false));
+ assertThat(objective.timeout(), is(5));
+ assertThat(objective.selector(), is(selector));
+ assertThat(objective.treatment(), is(treatment));
+ assertThat(objective.flag(), is(SPECIFIC));
+ assertThat(objective.appId(), is(APP_ID));
+ assertThat(objective.nextId(), is(33));
+ assertThat(objective.id(), is(not(0)));
+ assertThat(objective.priority(), is(22));
+ assertThat(objective.op(), is(op));
+ if (objective.context().isPresent()) {
+ assertThat(objective.context().get(), is(expectedContext));
+ } else {
+ assertThat(expectedContext, nullValue());
+ }
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * add() method.
+ */
+ @Test
+ public void testForwardingAdd() {
+ checkForwardingBase(baseForwardingBuilder().add(), ADD, null);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * add(context) method.
+ */
+ @Test
+ public void testForwardingAddWithContext() {
+ ObjectiveContext context = new MockObjectiveContext();
+ checkForwardingBase(baseForwardingBuilder().add(context), ADD, context);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * remove() method.
+ */
+ @Test
+ public void testForwardingRemove() {
+ checkForwardingBase(baseForwardingBuilder().remove(), REMOVE, null);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * remove(context) method.
+ */
+ @Test
+ public void testForwardingRemoveWithContext() {
+ ObjectiveContext context = new MockObjectiveContext();
+ checkForwardingBase(baseForwardingBuilder().remove(context), REMOVE, context);
+ }
+
+ // Filtering objectives
+
+ /**
+ * Makes a filtering objective builder with a set of default values.
+ *
+ * @return filtering objective builder
+ */
+ private FilteringObjective.Builder baseFilteringBuilder() {
+ return DefaultFilteringObjective.builder()
+ .withKey(key)
+ .withPriority(5)
+ .addCondition(criterion)
+ .fromApp(APP_ID)
+ .makeTemporary(2)
+ .deny();
+ }
+
+ /**
+ * Checks the default values of a filtering objective object.
+ *
+ * @param objective filtering objective to check
+ */
+ private void checkFilteringBase(FilteringObjective objective,
+ Objective.Operation op,
+ ObjectiveContext expectedContext) {
+ assertThat(objective.key(), is(key));
+ assertThat(objective.conditions(), hasItem(criterion));
+ assertThat(objective.permanent(), is(false));
+ assertThat(objective.timeout(), is(2));
+ assertThat(objective.priority(), is(5));
+ assertThat(objective.appId(), is(APP_ID));
+ assertThat(objective.type(), is(DENY));
+ assertThat(objective.id(), is(not(0)));
+ assertThat(objective.op(), is(op));
+ if (objective.context().isPresent()) {
+ assertThat(objective.context().get(), is(expectedContext));
+ } else {
+ assertThat(expectedContext, nullValue());
+ }
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * add() method.
+ */
+ @Test
+ public void testFilteringAdd() {
+ checkFilteringBase(baseFilteringBuilder().add(), ADD, null);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * add(context) method.
+ */
+ @Test
+ public void testFilteringAddWithContext() {
+ ObjectiveContext context = new MockObjectiveContext();
+ checkFilteringBase(baseFilteringBuilder().add(context), ADD, context);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * remove() method.
+ */
+ @Test
+ public void testFilteringRemove() {
+ checkFilteringBase(baseFilteringBuilder().remove(), REMOVE, null);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * remove(context) method.
+ */
+ @Test
+ public void testFilteringRemoveWithContext() {
+ ObjectiveContext context = new MockObjectiveContext();
+ checkFilteringBase(baseFilteringBuilder().remove(context), REMOVE, context);
+ }
+
+ // Next objectives
+
+ /**
+ * Makes a next objective builder with a set of default values.
+ *
+ * @return next objective builder
+ */
+ private NextObjective.Builder baseNextBuilder() {
+ return DefaultNextObjective.builder()
+ .addTreatment(treatment)
+ .withId(12)
+ .withType(HASHED)
+ .makeTemporary(777)
+ .withPriority(33)
+ .fromApp(APP_ID);
+ }
+
+ /**
+ * Checks the default values of a next objective object.
+ *
+ * @param objective next objective to check
+ */
+ private void checkNextBase(NextObjective objective,
+ Objective.Operation op,
+ ObjectiveContext expectedContext) {
+ assertThat(objective.id(), is(12));
+ assertThat(objective.appId(), is(APP_ID));
+ assertThat(objective.type(), is(HASHED));
+ assertThat(objective.next(), hasItem(treatment));
+ assertThat(objective.permanent(), is(false));
+ assertThat(objective.timeout(), is(0));
+ assertThat(objective.priority(), is(0));
+ assertThat(objective.op(), is(op));
+ if (objective.context().isPresent()) {
+ assertThat(objective.context().get(), is(expectedContext));
+ } else {
+ assertThat(expectedContext, nullValue());
+ }
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * add() method.
+ */
+ @Test
+ public void testNextAdd() {
+ checkNextBase(baseNextBuilder().add(), ADD, null);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * add(context) method.
+ */
+ @Test
+ public void testNextAddWithContext() {
+ ObjectiveContext context = new MockObjectiveContext();
+ checkNextBase(baseNextBuilder().add(context), ADD, context);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * remove() method.
+ */
+ @Test
+ public void testNextRemove() {
+ checkNextBase(baseNextBuilder().remove(), REMOVE, null);
+ }
+
+ /**
+ * Tests that forwarding objective objects are built correctly using the
+ * remove(context) method.
+ */
+ @Test
+ public void testNextRemoveWithContext() {
+ ObjectiveContext context = new MockObjectiveContext();
+ checkNextBase(baseNextBuilder().remove(context), REMOVE, context);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupDescriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupDescriptionTest.java
new file mode 100644
index 00000000..66b0089e
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupDescriptionTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Default group description unit tests.
+ */
+public class DefaultGroupDescriptionTest {
+ byte[] keyData = "abcdefg".getBytes();
+ private final GroupKey key = new DefaultGroupKey(keyData);
+ private final TrafficTreatment treatment =
+ DefaultTrafficTreatment.emptyTreatment();
+ private final GroupBucket bucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment);
+ private final GroupBuckets groupBuckets =
+ new GroupBuckets(ImmutableList.of(bucket));
+ private final DefaultGroupDescription d1 =
+ new DefaultGroupDescription(did("2"),
+ GroupDescription.Type.FAILOVER,
+ groupBuckets);
+ private final DefaultGroupDescription sameAsD1 =
+ new DefaultGroupDescription(d1);
+ private final DefaultGroupDescription d2 =
+ new DefaultGroupDescription(did("2"),
+ GroupDescription.Type.INDIRECT,
+ groupBuckets);
+ private final DefaultGroupDescription d3 =
+ new DefaultGroupDescription(did("3"),
+ GroupDescription.Type.FAILOVER,
+ groupBuckets,
+ key,
+ 711,
+ APP_ID);
+
+ /**
+ * Checks that the Default group description class is immutable and can be
+ * inherited from.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutableBaseClass(DefaultGroupDescription.class);
+ }
+
+ /**
+ * Tests for proper operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(d1, sameAsD1)
+ .addEqualityGroup(d2)
+ .addEqualityGroup(d3)
+ .testEquals();
+ }
+
+ /**
+ * Checks that construction of an object was correct.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(d3.deviceId(), is(did("3")));
+ assertThat(d3.type(), is(GroupDescription.Type.FAILOVER));
+ assertThat(d3.buckets(), is(groupBuckets));
+ assertThat(d3.appId(), is(APP_ID));
+ assertThat(d3.givenGroupId(), is(711));
+ assertThat(key.key(), is(keyData));
+ assertThat(d3.appCookie().key(), is(keyData));
+ }
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupTest.java
new file mode 100644
index 00000000..f27f266b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/DefaultGroupTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Unit tests for DefaultGroup class.
+ */
+public class DefaultGroupTest {
+ private final GroupId id1 = new DefaultGroupId(6);
+ private final GroupId id2 = new DefaultGroupId(7);
+ private final GroupBucket bucket =
+ DefaultGroupBucket.createSelectGroupBucket(
+ DefaultTrafficTreatment.emptyTreatment());
+ private final GroupBuckets groupBuckets =
+ new GroupBuckets(ImmutableList.of(bucket));
+ private final GroupDescription groupDesc1 =
+ new DefaultGroupDescription(did("1"),
+ GroupDescription.Type.FAILOVER,
+ groupBuckets);
+ private final GroupDescription groupDesc2 =
+ new DefaultGroupDescription(did("2"),
+ GroupDescription.Type.FAILOVER,
+ groupBuckets);
+
+ DefaultGroup group1 = new DefaultGroup(id1, groupDesc1);
+ DefaultGroup sameAsGroup1 = new DefaultGroup(id1, groupDesc1);
+ DefaultGroup group2 = new DefaultGroup(id1, groupDesc2);
+ DefaultGroup group3 = new DefaultGroup(id2, groupDesc2);
+
+ /**
+ * Tests for proper operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(group1, sameAsGroup1)
+ .addEqualityGroup(group2)
+ .addEqualityGroup(group3)
+ .testEquals();
+ }
+
+ /**
+ * Tests that objects are created properly.
+ */
+ @Test
+ public void checkConstruction() {
+ assertThat(group1.id(), is(id1));
+ assertThat(group1.bytes(), is(0L));
+ assertThat(group1.life(), is(0L));
+ assertThat(group1.packets(), is(0L));
+ assertThat(group1.referenceCount(), is(0L));
+ assertThat(group1.buckets(), is(groupBuckets));
+ assertThat(group1.state(), is(Group.GroupState.PENDING_ADD));
+ }
+
+ /**
+ * Tests that objects are created properly using the device based constructor.
+ */
+ @Test
+ public void checkConstructionWithDid() {
+ DefaultGroup group = new DefaultGroup(id2, NetTestTools.did("1"),
+ GroupDescription.Type.INDIRECT, groupBuckets);
+ assertThat(group.id(), is(id2));
+ assertThat(group.bytes(), is(0L));
+ assertThat(group.life(), is(0L));
+ assertThat(group.packets(), is(0L));
+ assertThat(group.referenceCount(), is(0L));
+ assertThat(group.deviceId(), is(NetTestTools.did("1")));
+ assertThat(group.buckets(), is(groupBuckets));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupBucketTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupBucketTest.java
new file mode 100644
index 00000000..0672ebf3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupBucketTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.net.group.GroupDescription.Type.FAILOVER;
+import static org.onosproject.net.group.GroupDescription.Type.INDIRECT;
+import static org.onosproject.net.group.GroupDescription.Type.SELECT;
+
+/**
+ * Unit tests for the group bucket class.
+ */
+public class GroupBucketTest {
+
+ private final GroupId groupId = new DefaultGroupId(7);
+ private final GroupId nullGroup = null;
+
+ private final PortNumber nullPort = null;
+
+ private final TrafficTreatment treatment =
+ DefaultTrafficTreatment.emptyTreatment();
+ private final GroupBucket selectGroupBucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment);
+ private final GroupBucket sameAsSelectGroupBucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment);
+ private final GroupBucket failoverGroupBucket =
+ DefaultGroupBucket.createFailoverGroupBucket(treatment,
+ PortNumber.IN_PORT, groupId);
+ private final GroupBucket indirectGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(treatment);
+ private final GroupBucket selectGroupBucketWithWeight =
+ DefaultGroupBucket.createSelectGroupBucket(treatment, (short) 5);
+
+
+ /**
+ * Tests for proper operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(selectGroupBucket,
+ sameAsSelectGroupBucket,
+ selectGroupBucketWithWeight)
+ .addEqualityGroup(failoverGroupBucket)
+ .addEqualityGroup(indirectGroupBucket)
+ .testEquals();
+ }
+
+ private void checkValues(GroupBucket bucket, GroupDescription.Type type,
+ long bytes, long packets, short weight,
+ GroupId groupId, PortNumber portNumber) {
+ assertThat(bucket.type(), is(type));
+ assertThat(bucket.bytes(), is(bytes));
+ assertThat(bucket.packets(), is(packets));
+ assertThat(bucket.treatment(), is(treatment));
+ assertThat(bucket.weight(), is(weight));
+ assertThat(bucket.watchGroup(), is(groupId));
+ assertThat(bucket.watchPort(), is(portNumber));
+ }
+
+ /**
+ * Checks that construction of a select group was correct.
+ */
+ @Test
+ public void checkSelectGroup() {
+ // Casting needed because only the store accesses the set methods.
+ ((DefaultGroupBucket) selectGroupBucket).setBytes(4);
+ ((DefaultGroupBucket) selectGroupBucket).setPackets(44);
+
+ checkValues(selectGroupBucket, SELECT, 4L, 44L, (short) 1,
+ nullGroup, nullPort);
+ }
+
+ /**
+ * Checks that construction of a select group with a weight was correct.
+ */
+ @Test
+ public void checkSelectGroupWithPriority() {
+ checkValues(selectGroupBucketWithWeight, SELECT, 0L, 0L, (short) 5,
+ nullGroup, nullPort);
+ }
+
+ /**
+ * Checks that construction of an indirect group was correct.
+ */
+ @Test
+ public void checkFailoverGroup() {
+ checkValues(failoverGroupBucket, FAILOVER, 0L, 0L, (short) -1,
+ groupId, PortNumber.IN_PORT);
+ }
+ /**
+ * Checks that construction of an indirect group was correct.
+ */
+ @Test
+ public void checkIndirectGroup() {
+ checkValues(indirectGroupBucket, INDIRECT, 0L, 0L, (short) -1,
+ nullGroup, nullPort);
+ }
+
+ /**
+ * Checks that a weight of 0 results in no group getting created.
+ */
+ @Test
+ public void checkZeroWeight() {
+ GroupBucket bucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment, (short) 0);
+ assertThat(bucket, nullValue());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupOperationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupOperationTest.java
new file mode 100644
index 00000000..5ee3a7ec
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/group/GroupOperationTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.group.GroupDescription.Type.ALL;
+import static org.onosproject.net.group.GroupDescription.Type.INDIRECT;
+import static org.onosproject.net.group.GroupOperation.Type.ADD;
+
+/**
+ * Tests for the group operation class.
+ */
+public class GroupOperationTest {
+
+ private final GroupId groupId = new DefaultGroupId(6);
+ private final TrafficTreatment treatment =
+ DefaultTrafficTreatment.emptyTreatment();
+ private final GroupBucket bucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment);
+ private final GroupBuckets groupBuckets =
+ new GroupBuckets(ImmutableList.of(bucket));
+ private final GroupOperation op1 =
+ GroupOperation.createAddGroupOperation(groupId, ALL, groupBuckets);
+ private final GroupOperation sameAsOp1 =
+ GroupOperation.createAddGroupOperation(groupId, ALL, groupBuckets);
+ private final GroupOperation op2 =
+ GroupOperation.createAddGroupOperation(groupId, INDIRECT, groupBuckets);
+ private final GroupOperation op3 =
+ GroupOperation.createDeleteGroupOperation(groupId, INDIRECT);
+ private final GroupOperation op4 =
+ GroupOperation.createModifyGroupOperation(groupId, INDIRECT, groupBuckets);
+
+ /**
+ * Checks that the GroupOperation class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(GroupOperation.class);
+ }
+
+ /**
+ * Tests for proper operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(op1, sameAsOp1)
+ .addEqualityGroup(op2)
+ .addEqualityGroup(op3)
+ .addEqualityGroup(op4)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the construction of the add operation is correct.
+ */
+ @Test
+ public void testAddGroupOperation() {
+ assertThat(op1.buckets(), is(groupBuckets));
+ assertThat(op1.groupId(), is(groupId));
+ assertThat(op1.groupType(), is(ALL));
+ assertThat(op1.opType(), is(ADD));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/DefaultHostDecriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/DefaultHostDecriptionTest.java
new file mode 100644
index 00000000..496269b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/DefaultHostDecriptionTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for the default host description.
+ */
+public class DefaultHostDecriptionTest {
+
+ 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 HostLocation LOC = new HostLocation(
+ DeviceId.deviceId("of:foo"),
+ PortNumber.portNumber(100),
+ 123L
+ );
+
+ @Test
+ public void basics() {
+ HostDescription host =
+ new DefaultHostDescription(MAC, VLAN, LOC, IP);
+ assertEquals("incorrect mac", MAC, host.hwAddress());
+ assertEquals("incorrect vlan", VLAN, host.vlan());
+ assertEquals("incorrect location", LOC, host.location());
+ assertEquals("incorrect ip's", ImmutableSet.of(IP), host.ipAddress());
+ assertTrue("incorrect toString", host.toString().contains("vlan=10"));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostEventTest.java
new file mode 100644
index 00000000..4a55d638
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostEventTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.junit.Test;
+import org.onosproject.event.AbstractEventTest;
+import org.onosproject.net.DefaultHost;
+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.provider.ProviderId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import com.google.common.collect.Sets;
+
+public class HostEventTest extends AbstractEventTest {
+
+ private Host createHost() {
+ MacAddress mac = MacAddress.valueOf("00:00:11:00:00:01");
+ VlanId vlan = VlanId.vlanId((short) 10);
+ HostLocation loc = new HostLocation(
+ DeviceId.deviceId("of:foo"),
+ PortNumber.portNumber(100),
+ 123L
+ );
+ Set<IpAddress> ipset = Sets.newHashSet(
+ IpAddress.valueOf("10.0.0.1"),
+ IpAddress.valueOf("10.0.0.2")
+ );
+ HostId hid = HostId.hostId(mac, vlan);
+
+ return new DefaultHost(
+ new ProviderId("of", "foo"), hid, mac, vlan, loc, ipset);
+ }
+
+ @Override
+ @Test
+ public void withTime() {
+ Host host = createHost();
+ HostEvent event = new HostEvent(HostEvent.Type.HOST_ADDED, host, 123L);
+ validateEvent(event, HostEvent.Type.HOST_ADDED, host, 123L);
+ }
+
+ @Override
+ @Test
+ public void withoutTime() {
+ Host host = createHost();
+ long before = System.currentTimeMillis();
+ HostEvent event = new HostEvent(HostEvent.Type.HOST_ADDED, host, before);
+ long after = System.currentTimeMillis();
+ validateEvent(event, HostEvent.Type.HOST_ADDED, host, before, after);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostServiceAdapter.java
new file mode 100644
index 00000000..226dad06
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/HostServiceAdapter.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+/**
+ * Test adapter for host service.
+ */
+public class HostServiceAdapter implements HostService {
+ @Override
+ public int getHostCount() {
+ return 0;
+ }
+
+ @Override
+ public Iterable<Host> getHosts() {
+ return null;
+ }
+
+ @Override
+ public Host getHost(HostId hostId) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getHostsByVlan(VlanId vlanId) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getHostsByMac(MacAddress mac) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getHostsByIp(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public void startMonitoringIp(IpAddress ip) {
+ }
+
+ @Override
+ public void stopMonitoringIp(IpAddress ip) {
+ }
+
+ @Override
+ public void requestMac(IpAddress ip) {
+ }
+
+ @Override
+ public void addListener(HostListener listener) {
+ }
+
+ @Override
+ public void removeListener(HostListener listener) {
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindings() {
+ return null;
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
+ return null;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/InterfaceIpAddressTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/InterfaceIpAddressTest.java
new file mode 100644
index 00000000..6120b7ce
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/InterfaceIpAddressTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for class {@link InterfaceIpAddress}.
+ */
+public class InterfaceIpAddressTest {
+ private static final IpAddress IP_ADDRESS = IpAddress.valueOf("1.2.3.4");
+ private static final IpPrefix SUBNET_ADDRESS =
+ IpPrefix.valueOf("1.2.0.0/16");
+ private static final IpAddress BROADCAST_ADDRESS =
+ IpAddress.valueOf("1.2.0.255"); // NOTE: non-default broadcast
+ private static final IpAddress PEER_ADDRESS = IpAddress.valueOf("5.6.7.8");
+
+ private static final IpAddress IP_ADDRESS2 = IpAddress.valueOf("10.2.3.4");
+ private static final IpPrefix SUBNET_ADDRESS2 =
+ IpPrefix.valueOf("10.2.0.0/16");
+ private static final IpAddress BROADCAST_ADDRESS2 =
+ IpAddress.valueOf("10.2.0.255"); // NOTE: non-default broadcast
+ private static final IpAddress PEER_ADDRESS2 =
+ IpAddress.valueOf("50.6.7.8");
+
+ /**
+ * Tests valid class copy constructor.
+ */
+ @Test
+ public void testCopyConstructor() {
+ InterfaceIpAddress fromAddr;
+ InterfaceIpAddress toAddr;
+
+ // Regular interface address with default broadcast address
+ fromAddr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+ toAddr = new InterfaceIpAddress(fromAddr);
+ assertThat(toAddr.ipAddress(), is(fromAddr.ipAddress()));
+ assertThat(toAddr.subnetAddress(), is(fromAddr.subnetAddress()));
+ assertThat(toAddr.broadcastAddress(), is(fromAddr.broadcastAddress()));
+ assertThat(toAddr.peerAddress(), is(fromAddr.peerAddress()));
+
+ // Interface address with non-default broadcast address
+ fromAddr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+ toAddr = new InterfaceIpAddress(fromAddr);
+ assertThat(toAddr.ipAddress(), is(fromAddr.ipAddress()));
+ assertThat(toAddr.subnetAddress(), is(fromAddr.subnetAddress()));
+ assertThat(toAddr.broadcastAddress(), is(fromAddr.broadcastAddress()));
+ assertThat(toAddr.peerAddress(), is(fromAddr.peerAddress()));
+
+ // Point-to-point address with peer IP address
+ fromAddr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+ toAddr = new InterfaceIpAddress(fromAddr);
+ assertThat(toAddr.ipAddress(), is(fromAddr.ipAddress()));
+ assertThat(toAddr.subnetAddress(), is(fromAddr.subnetAddress()));
+ assertThat(toAddr.broadcastAddress(), is(fromAddr.broadcastAddress()));
+ assertThat(toAddr.peerAddress(), is(fromAddr.peerAddress()));
+ }
+
+ /**
+ * Tests invalid class copy constructor for a null object to copy from.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullObject() {
+ InterfaceIpAddress fromAddr = null;
+ InterfaceIpAddress toAddr = new InterfaceIpAddress(fromAddr);
+ }
+
+ /**
+ * Tests valid class constructor for regular interface address with
+ * default broadcast address.
+ */
+ @Test
+ public void testConstructorForDefaultBroadcastAddress() {
+ InterfaceIpAddress addr =
+ new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+ assertThat(addr.ipAddress(), is(IP_ADDRESS));
+ assertThat(addr.subnetAddress(), is(SUBNET_ADDRESS));
+ assertThat(addr.broadcastAddress(), nullValue());
+ assertThat(addr.peerAddress(), nullValue());
+ }
+
+ /**
+ * Tests valid class constructor for interface address with
+ * non-default broadcast address.
+ */
+ @Test
+ public void testConstructorForNonDefaultBroadcastAddress() {
+ InterfaceIpAddress addr =
+ new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+
+ assertThat(addr.ipAddress(), is(IP_ADDRESS));
+ assertThat(addr.subnetAddress(), is(SUBNET_ADDRESS));
+ assertThat(addr.broadcastAddress(), is(BROADCAST_ADDRESS));
+ assertThat(addr.peerAddress(), nullValue());
+ }
+
+ /**
+ * Tests valid class constructor for point-to-point interface address with
+ * peer address.
+ */
+ @Test
+ public void testConstructorForPointToPointAddress() {
+ InterfaceIpAddress addr =
+ new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+
+ assertThat(addr.ipAddress(), is(IP_ADDRESS));
+ assertThat(addr.subnetAddress(), is(SUBNET_ADDRESS));
+ assertThat(addr.broadcastAddress(), nullValue());
+ assertThat(addr.peerAddress(), is(PEER_ADDRESS));
+ }
+
+ /**
+ * Tests getting the fields of an interface address.
+ */
+ @Test
+ public void testGetFields() {
+ InterfaceIpAddress addr;
+
+ // Regular interface address with default broadcast address
+ addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+ assertThat(addr.ipAddress().toString(), is("1.2.3.4"));
+ assertThat(addr.subnetAddress().toString(), is("1.2.0.0/16"));
+ assertThat(addr.broadcastAddress(), is(nullValue())); // TODO: Fix
+ assertThat(addr.peerAddress(), is(nullValue()));
+
+ // Interface address with non-default broadcast address
+ addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+ assertThat(addr.ipAddress().toString(), is("1.2.3.4"));
+ assertThat(addr.subnetAddress().toString(), is("1.2.0.0/16"));
+ assertThat(addr.broadcastAddress().toString(), is("1.2.0.255"));
+ assertThat(addr.peerAddress(), is(nullValue()));
+
+ // Point-to-point address with peer IP address
+ addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+ assertThat(addr.ipAddress().toString(), is("1.2.3.4"));
+ assertThat(addr.subnetAddress().toString(), is("1.2.0.0/16"));
+ assertThat(addr.broadcastAddress(), is(nullValue()));
+ assertThat(addr.peerAddress().toString(), is("5.6.7.8"));
+ }
+
+ /**
+ * Tests equality of {@link InterfaceIpAddress}.
+ */
+ @Test
+ public void testEquality() {
+ InterfaceIpAddress addr1, addr2;
+
+ // Regular interface address with default broadcast address
+ addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+ addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+ assertThat(addr1, is(addr2));
+
+ // Interface address with non-default broadcast address
+ addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+ addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+ assertThat(addr1, is(addr2));
+
+ // Point-to-point address with peer IP address
+ addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+ addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+ assertThat(addr1, is(addr2));
+ }
+
+ /**
+ * Tests non-equality of {@link InterfaceIpAddress}.
+ */
+ @Test
+ public void testNonEquality() {
+ InterfaceIpAddress addr1, addr2, addr3, addr4;
+
+ // Regular interface address with default broadcast address
+ addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+ // Interface address with non-default broadcast address
+ addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+ // Point-to-point address with peer IP address
+ addr3 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+
+ // Test interface addresses with different properties:
+ // - default-broadcast vs non-default broadcast
+ // - regular vs point-to-point
+ assertThat(addr1, is(not(addr2)));
+ assertThat(addr1, is(not(addr3)));
+ assertThat(addr2, is(not(addr3)));
+
+ // Test regular interface address with default broadcast address
+ addr4 = new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS);
+ assertThat(addr1, is(not(addr4)));
+ addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS2);
+ assertThat(addr1, is(not(addr4)));
+
+ // Test interface address with non-default broadcast address
+ addr4 = new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS);
+ assertThat(addr2, is(not(addr4)));
+ addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS2,
+ BROADCAST_ADDRESS);
+ assertThat(addr2, is(not(addr4)));
+ addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+ BROADCAST_ADDRESS2);
+ assertThat(addr2, is(not(addr4)));
+
+ // Test point-to-point address with peer IP address
+ addr4 = new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS, null,
+ PEER_ADDRESS);
+ assertThat(addr3, is(not(addr4)));
+ addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS2, null,
+ PEER_ADDRESS);
+ assertThat(addr3, is(not(addr4)));
+ addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+ PEER_ADDRESS2);
+ assertThat(addr3, is(not(addr4)));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/PortAddressesTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/PortAddressesTest.java
new file mode 100644
index 00000000..7c10cd15
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/host/PortAddressesTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.host;
+
+import java.util.Set;
+
+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.net.ConnectPoint;
+import org.onosproject.net.NetTestTools;
+
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Unit tests for port addresses class.
+ */
+public class PortAddressesTest {
+
+ PortAddresses addresses1;
+ PortAddresses sameAsAddresses1;
+ PortAddresses addresses2;
+ PortAddresses addresses3;
+
+ private static final ConnectPoint CONNECT_POINT1 =
+ NetTestTools.connectPoint("cp1", 1);
+ private static final IpAddress IP_ADDRESS1 = IpAddress.valueOf("1.2.3.4");
+ private static final IpPrefix SUBNET_ADDRESS1 =
+ IpPrefix.valueOf("1.2.0.0/16");
+ private static final InterfaceIpAddress INTERFACE_ADDRESS_1 =
+ new InterfaceIpAddress(IP_ADDRESS1, SUBNET_ADDRESS1);
+
+ private static final ConnectPoint CONNECT_POINT2 =
+ NetTestTools.connectPoint("cp2", 1);
+ private static final IpAddress IP_ADDRESS2 = IpAddress.valueOf("1.2.3.5");
+ private static final IpPrefix SUBNET_ADDRESS2 =
+ IpPrefix.valueOf("1.3.0.0/16");
+ private static final InterfaceIpAddress INTERFACE_ADDRESS_2 =
+ new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS2);
+
+ Set<InterfaceIpAddress> ipAddresses;
+
+
+ /**
+ * Initializes local data used by all test cases.
+ */
+ @Before
+ public void setUpAddresses() {
+ ipAddresses = ImmutableSet.of(INTERFACE_ADDRESS_1,
+ INTERFACE_ADDRESS_2);
+ addresses1 = new PortAddresses(CONNECT_POINT1, ipAddresses,
+ MacAddress.BROADCAST, VlanId.NONE);
+ sameAsAddresses1 = new PortAddresses(CONNECT_POINT1, ipAddresses,
+ MacAddress.BROADCAST, VlanId.NONE);
+ addresses2 = new PortAddresses(CONNECT_POINT2, ipAddresses,
+ MacAddress.BROADCAST, VlanId.NONE);
+ addresses3 = new PortAddresses(CONNECT_POINT2, ipAddresses,
+ MacAddress.ZERO, VlanId.NONE);
+ }
+
+ /**
+ * Checks that the PortAddresses class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(PortAddresses.class);
+ }
+
+ /**
+ * Checks the operation of the equals(), hash() and toString()
+ * methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(addresses1, sameAsAddresses1)
+ .addEqualityGroup(addresses2)
+ .addEqualityGroup(addresses3)
+ .testEquals();
+ }
+
+ /**
+ * Tests that object are created correctly.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(addresses1.mac(), is(MacAddress.BROADCAST));
+ assertThat(addresses1.connectPoint(), is(CONNECT_POINT1));
+ assertThat(addresses1.ipAddresses(), is(ipAddresses));
+ assertThat(addresses1.vlan(), is(VlanId.NONE));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/AbstractIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/AbstractIntentTest.java
new file mode 100644
index 00000000..6bb7e28f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/AbstractIntentTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.junit.After;
+import org.junit.Before;
+import org.onosproject.core.IdGenerator;
+
+public abstract class AbstractIntentTest {
+
+ protected IdGenerator idGenerator = new MockIdGenerator();
+
+ @Before
+ public void setUp() throws Exception {
+ Intent.bindIdGenerator(idGenerator);
+ }
+
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java
new file mode 100644
index 00000000..e03ed850
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+/**
+ * Base facilities to test various connectivity tests.
+ */
+public abstract class ConnectivityIntentTest extends IntentTest {
+
+ public static final ApplicationId APPID = new TestApplicationId("foo");
+
+ public static final IntentId IID = new IntentId(123);
+ public static final TrafficSelector MATCH = DefaultTrafficSelector.emptySelector();
+ public static final TrafficTreatment NOP = DefaultTrafficTreatment.emptyTreatment();
+
+ public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1));
+ public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2));
+ public static final ConnectPoint P3 = new ConnectPoint(DeviceId.deviceId("333"), PortNumber.portNumber(0x3));
+
+ public static final Set<ConnectPoint> PS1 = itemSet(new ConnectPoint[]{P1, P3});
+ public static final Set<ConnectPoint> PS2 = itemSet(new ConnectPoint[]{P2, P3});
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java
new file mode 100644
index 00000000..9b9f7cec
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java
@@ -0,0 +1,268 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Fake implementation of the intent service to assist in developing tests of
+ * the interface contract.
+ */
+public class FakeIntentManager implements TestableIntentService {
+
+ private final Map<Key, Intent> intents = new HashMap<>();
+ private final Map<Key, IntentState> intentStates = new HashMap<>();
+ private final Map<Key, List<Intent>> installables = new HashMap<>();
+ private final Set<IntentListener> listeners = new HashSet<>();
+
+ private final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = new HashMap<>();
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final List<IntentException> exceptions = new ArrayList<>();
+
+ @Override
+ public List<IntentException> getExceptions() {
+ return exceptions;
+ }
+
+ // Provides an out-of-thread simulation of intent submit life-cycle
+ private void executeSubmit(final Intent intent) {
+ registerSubclassCompilerIfNeeded(intent);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ executeCompilingPhase(intent);
+ } catch (IntentException e) {
+ exceptions.add(e);
+ }
+ }
+ });
+ }
+
+ // Provides an out-of-thread simulation of intent withdraw life-cycle
+ private void executeWithdraw(final Intent intent) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ List<Intent> installable = getInstallable(intent.key());
+ executeWithdrawingPhase(intent, installable);
+ } catch (IntentException e) {
+ exceptions.add(e);
+ }
+
+ }
+ });
+ }
+
+ 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;
+ }
+
+ private <T extends Intent> void executeCompilingPhase(T intent) {
+ setState(intent, IntentState.COMPILING);
+ try {
+ // For the fake, we compile using a single level pass
+ List<Intent> installable = new ArrayList<>();
+ for (Intent compiled : getCompiler(intent).compile(intent, null, null)) {
+ installable.add(compiled);
+ }
+ executeInstallingPhase(intent, installable);
+
+ } catch (IntentException e) {
+ setState(intent, IntentState.FAILED);
+ dispatch(new IntentEvent(IntentEvent.Type.FAILED, intent));
+ }
+ }
+
+ private void executeInstallingPhase(Intent intent,
+ List<Intent> installable) {
+ setState(intent, IntentState.INSTALLING);
+ try {
+ setState(intent, IntentState.INSTALLED);
+ putInstallable(intent.key(), installable);
+ dispatch(new IntentEvent(IntentEvent.Type.INSTALLED, intent));
+
+ } catch (IntentException e) {
+ setState(intent, IntentState.FAILED);
+ dispatch(new IntentEvent(IntentEvent.Type.FAILED, intent));
+ }
+ }
+
+ private void executeWithdrawingPhase(Intent intent,
+ List<Intent> installable) {
+ setState(intent, IntentState.WITHDRAWING);
+ try {
+ removeInstallable(intent.key());
+ setState(intent, IntentState.WITHDRAWN);
+ dispatch(new IntentEvent(IntentEvent.Type.WITHDRAWN, intent));
+ } catch (IntentException e) {
+ // FIXME: Rework this to always go from WITHDRAWING to WITHDRAWN!
+ setState(intent, IntentState.FAILED);
+ dispatch(new IntentEvent(IntentEvent.Type.FAILED, intent));
+ }
+ }
+
+ // Sets the internal state for the given intent and dispatches an event
+ private void setState(Intent intent, IntentState state) {
+ intentStates.put(intent.key(), state);
+ }
+
+ private void putInstallable(Key key, List<Intent> installable) {
+ installables.put(key, installable);
+ }
+
+ private void removeInstallable(Key key) {
+ installables.remove(key);
+ }
+
+ private List<Intent> getInstallable(Key key) {
+ List<Intent> installable = installables.get(key);
+ if (installable != null) {
+ return installable;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void submit(Intent intent) {
+ intents.put(intent.key(), intent);
+ setState(intent, IntentState.INSTALL_REQ);
+ dispatch(new IntentEvent(IntentEvent.Type.INSTALL_REQ, intent));
+ executeSubmit(intent);
+ }
+
+ @Override
+ public void withdraw(Intent intent) {
+ intents.remove(intent.key());
+ executeWithdraw(intent);
+ }
+
+ @Override
+ public void purge(Intent intent) {
+ IntentState currentState = intentStates.get(intent.key());
+ if (currentState == IntentState.WITHDRAWN
+ || currentState == IntentState.FAILED) {
+ intents.remove(intent.key());
+ installables.remove(intent.key());
+ intentStates.remove(intent.key());
+ }
+ }
+
+ @Override
+ public Set<Intent> getIntents() {
+ return Collections.unmodifiableSet(new HashSet<>(intents.values()));
+ }
+
+ @Override
+ public Iterable<IntentData> getIntentData() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getIntentCount() {
+ return intents.size();
+ }
+
+ @Override
+ public Intent getIntent(Key intentKey) {
+ return intents.get(intentKey);
+ }
+
+ @Override
+ public IntentState getIntentState(Key intentKey) {
+ return intentStates.get(intentKey);
+ }
+
+ @Override
+ public List<Intent> getInstallableIntents(Key intentKey) {
+ return installables.get(intentKey);
+ }
+
+ @Override
+ public boolean isLocal(Key intentKey) {
+ return true;
+ }
+
+ @Override
+ public Iterable<Intent> getPending() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void addListener(IntentListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(IntentListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void dispatch(IntentEvent event) {
+ for (IntentListener listener : listeners) {
+ listener.event(event);
+ }
+ }
+
+ @Override
+ public <T extends Intent> void registerCompiler(Class<T> cls,
+ IntentCompiler<T> compiler) {
+ compilers.put(cls, compiler);
+ }
+
+ @Override
+ public <T extends Intent> void unregisterCompiler(Class<T> cls) {
+ compilers.remove(cls);
+ }
+
+ @Override
+ public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
+ return Collections.unmodifiableMap(compilers);
+ }
+
+ 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/api/src/test/java/org/onosproject/net/intent/HostToHostIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/HostToHostIntentTest.java
new file mode 100644
index 00000000..3f7650e4
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/HostToHostIntentTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.flow.TrafficSelector;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.hid;
+
+/**
+ * Unit tests for the HostToHostIntent class.
+ */
+public class HostToHostIntentTest extends IntentTest {
+ private final TrafficSelector selector = new IntentTestsMocks.MockSelector();
+ private final IntentTestsMocks.MockTreatment treatment = new IntentTestsMocks.MockTreatment();
+ private final HostId id1 = hid("12:34:56:78:91:ab/1");
+ private final HostId id2 = hid("12:34:56:78:92:ab/1");
+ private final HostId id3 = hid("12:34:56:78:93:ab/1");
+
+ private static final ApplicationId APPID = new TestApplicationId("foo");
+
+ private HostToHostIntent makeHostToHost(HostId one, HostId two) {
+ return HostToHostIntent.builder()
+ .appId(APPID)
+ .one(one)
+ .two(two)
+ .selector(selector)
+ .treatment(treatment)
+ .build();
+ }
+
+ /**
+ * Tests the equals() method where two HostToHostIntents have references
+ * to the same hosts. These should compare equal.
+ */
+ @Test
+ public void testSameEquals() {
+
+ HostId one = hid("00:00:00:00:00:01/-1");
+ HostId two = hid("00:00:00:00:00:02/-1");
+ HostToHostIntent i1 = makeHostToHost(one, two);
+ HostToHostIntent i2 = makeHostToHost(one, two);
+
+ assertThat(i1.one(), is(equalTo(i2.one())));
+ assertThat(i1.two(), is(equalTo(i2.two())));
+ }
+
+ /**
+ * Checks that the HostToHostIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(HostToHostIntent.class);
+ }
+
+ /**
+ * Tests equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ final HostToHostIntent intent1 = HostToHostIntent.builder()
+ .appId(APPID)
+ .one(id1)
+ .two(id2)
+ .selector(selector)
+ .treatment(treatment)
+ .build();
+
+ final HostToHostIntent intent2 = HostToHostIntent.builder()
+ .appId(APPID)
+ .one(id2)
+ .two(id3)
+ .selector(selector)
+ .treatment(treatment)
+ .build();
+
+ new EqualsTester()
+ .addEqualityGroup(intent1)
+ .addEqualityGroup(intent2)
+ .testEquals();
+ }
+
+ @Override
+ protected Intent createOne() {
+ return HostToHostIntent.builder()
+ .appId(APPID)
+ .one(id1)
+ .two(id2)
+ .selector(selector)
+ .treatment(treatment)
+ .build();
+ }
+
+ @Override
+ protected Intent createAnother() {
+ return HostToHostIntent.builder()
+ .appId(APPID)
+ .one(id1)
+ .two(id3)
+ .selector(selector)
+ .treatment(treatment)
+ .build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentDataTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentDataTest.java
new file mode 100644
index 00000000..9c4cf7e0
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentDataTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.store.Timestamp;
+
+import com.google.common.testing.EqualsTester;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
+import static org.onosproject.net.intent.IntentTestsMocks.MockTimestamp;
+
+/**
+ * Unit tests for intent data objects.
+ */
+public class IntentDataTest {
+
+ private Timestamp timestamp1;
+ private Timestamp timestamp2;
+ private Timestamp timestamp3;
+
+ private Intent intent1;
+ private Intent intent2;
+ private Intent intent3;
+
+ private IntentData data1;
+ private IntentData data1Copy;
+ private IntentData data2;
+ private IntentData data2Copy;
+ private IntentData data3;
+ private IntentData data3Copy;
+
+ IdGenerator idGenerator;
+
+ @Before
+ public void setUpTest() {
+ idGenerator = new MockIdGenerator();
+ Intent.bindIdGenerator(idGenerator);
+
+ timestamp1 = new MockTimestamp(1);
+ timestamp2 = new MockTimestamp(2);
+ timestamp3 = new MockTimestamp(3);
+
+ intent1 = new MockIntent(1L);
+ intent2 = new MockIntent(2L);
+ intent3 = new MockIntent(3L);
+
+ data1 = new IntentData(intent1, IntentState.INSTALLED, timestamp1);
+ data1Copy = new IntentData(intent1, IntentState.INSTALLED, timestamp1);
+ data2 = new IntentData(intent2, IntentState.INSTALLED, timestamp2);
+ data2Copy = new IntentData(intent2, IntentState.INSTALLED, timestamp2);
+ data3 = new IntentData(intent3, IntentState.INSTALLED, timestamp3);
+ data3Copy = new IntentData(intent3, IntentState.INSTALLED, timestamp3);
+ }
+
+ @After
+ public void tearDownTest() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Checks that intent data objects are properly constructed.
+ */
+ @Test
+ public void checkConstruction() {
+ assertThat(data1.state(), is(IntentState.INSTALLED));
+ assertThat(data1.version(), is(timestamp1));
+ assertThat(data1.intent(), is(intent1));
+ }
+
+ /**
+ * Checks equals() for intent data objects.
+ */
+ @Test
+ public void checkEquals() {
+ new EqualsTester()
+ .addEqualityGroup(data1, data1Copy)
+ .addEqualityGroup(data2, data2Copy)
+ .addEqualityGroup(data3, data3Copy)
+ .testEquals();
+ }
+
+ @Test
+ public void testIsUpdateAcceptable() {
+ // Going from null to something is always allowed
+ assertTrue(IntentData.isUpdateAcceptable(null, data1));
+
+ // we can go from older version to newer but not they other way
+ assertTrue(IntentData.isUpdateAcceptable(data1, data2));
+ assertFalse(IntentData.isUpdateAcceptable(data2, data1));
+
+ IntentData installing = new IntentData(intent1, IntentState.INSTALLING, timestamp1);
+ IntentData installed = new IntentData(intent1, IntentState.INSTALLED, timestamp1);
+ IntentData withdrawing = new IntentData(intent1, IntentState.WITHDRAWING, timestamp1);
+ IntentData withdrawn = new IntentData(intent1, IntentState.WITHDRAWN, timestamp1);
+
+ IntentData failed = new IntentData(intent1, IntentState.FAILED, timestamp1);
+ IntentData purgeReq = new IntentData(intent1, IntentState.PURGE_REQ, timestamp1);
+
+ IntentData compiling = new IntentData(intent1, IntentState.COMPILING, timestamp1);
+ IntentData recompiling = new IntentData(intent1, IntentState.RECOMPILING, timestamp1);
+ IntentData installReq = new IntentData(intent1, IntentState.INSTALL_REQ, timestamp1);
+ IntentData withdrawReq = new IntentData(intent1, IntentState.WITHDRAW_REQ, timestamp1);
+
+ // We can't change to the same state
+ assertFalse(IntentData.isUpdateAcceptable(installing, installing));
+ assertFalse(IntentData.isUpdateAcceptable(installed, installed));
+
+ // From installing we can change to installed
+ assertTrue(IntentData.isUpdateAcceptable(installing, installed));
+
+ // Sanity checks in case the manager submits bogus state transitions
+ assertFalse(IntentData.isUpdateAcceptable(installing, withdrawing));
+ assertFalse(IntentData.isUpdateAcceptable(installing, withdrawn));
+ assertFalse(IntentData.isUpdateAcceptable(installed, withdrawing));
+ assertFalse(IntentData.isUpdateAcceptable(installed, withdrawn));
+
+ // We can't change to the same state
+ assertFalse(IntentData.isUpdateAcceptable(withdrawing, withdrawing));
+ assertFalse(IntentData.isUpdateAcceptable(withdrawn, withdrawn));
+
+ // From withdrawing we can change to withdrawn
+ assertTrue(IntentData.isUpdateAcceptable(withdrawing, withdrawn));
+
+ // Sanity checks in case the manager submits bogus state transitions
+ assertFalse(IntentData.isUpdateAcceptable(withdrawing, installing));
+ assertFalse(IntentData.isUpdateAcceptable(withdrawing, installed));
+ assertFalse(IntentData.isUpdateAcceptable(withdrawn, installing));
+ assertFalse(IntentData.isUpdateAcceptable(withdrawn, installed));
+
+ // We can't go from failed to failed
+ assertFalse(IntentData.isUpdateAcceptable(failed, failed));
+
+ // But we can go from any install* or withdraw* state to failed
+ assertTrue(IntentData.isUpdateAcceptable(installing, failed));
+ assertTrue(IntentData.isUpdateAcceptable(installed, failed));
+ assertTrue(IntentData.isUpdateAcceptable(withdrawing, failed));
+ assertTrue(IntentData.isUpdateAcceptable(withdrawn, failed));
+
+ // We can go from anything to purgeReq
+ assertTrue(IntentData.isUpdateAcceptable(installing, purgeReq));
+ assertTrue(IntentData.isUpdateAcceptable(installed, purgeReq));
+ assertTrue(IntentData.isUpdateAcceptable(withdrawing, purgeReq));
+ assertTrue(IntentData.isUpdateAcceptable(withdrawn, purgeReq));
+ assertTrue(IntentData.isUpdateAcceptable(failed, purgeReq));
+
+ // We can't go from purgeReq back to anything else
+ assertFalse(IntentData.isUpdateAcceptable(purgeReq, withdrawn));
+ assertFalse(IntentData.isUpdateAcceptable(purgeReq, withdrawing));
+ assertFalse(IntentData.isUpdateAcceptable(purgeReq, installed));
+ assertFalse(IntentData.isUpdateAcceptable(purgeReq, installing));
+
+ // We're never allowed to store transient states
+ assertFalse(IntentData.isUpdateAcceptable(installing, compiling));
+ assertFalse(IntentData.isUpdateAcceptable(installing, recompiling));
+ assertFalse(IntentData.isUpdateAcceptable(installing, installReq));
+ assertFalse(IntentData.isUpdateAcceptable(installing, withdrawReq));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentExceptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentExceptionTest.java
new file mode 100644
index 00000000..f26ee639
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentExceptionTest.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.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the intent exception.
+ */
+public class IntentExceptionTest {
+
+ @Test
+ public void basics() {
+ validate(new IntentException(), null, null);
+ validate(new IntentException("foo"), "foo", null);
+
+ Throwable cause = new NullPointerException("bar");
+ validate(new IntentException("foo", cause), "foo", cause);
+ }
+
+ /**
+ * Validates that the specified exception has the correct message and cause.
+ *
+ * @param e exception to test
+ * @param message expected message
+ * @param cause expected cause
+ */
+ protected void validate(RuntimeException e, String message, Throwable cause) {
+ assertEquals("incorrect message", message, e.getMessage());
+ assertEquals("incorrect cause", cause, e.getCause());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentIdTest.java
new file mode 100644
index 00000000..e2987b54
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentIdTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * This class tests the immutability, equality, and non-equality of
+ * {@link IntentId}.
+ */
+public class IntentIdTest {
+ /**
+ * Tests the immutability of {@link IntentId}.
+ */
+ @Test
+ public void intentIdFollowsGuidelineForImmutableObject() {
+ assertThatClassIsImmutable(IntentId.class);
+ }
+
+ /**
+ * Tests equality of {@link IntentId}.
+ */
+ @Test
+ public void testEquality() {
+ IntentId id1 = new IntentId(1L);
+ IntentId id2 = new IntentId(1L);
+
+ assertThat(id1, is(id2));
+ }
+
+ /**
+ * Tests non-equality of {@link IntentId}.
+ */
+ @Test
+ public void testNonEquality() {
+ IntentId id1 = new IntentId(1L);
+ IntentId id2 = new IntentId(2L);
+
+ assertThat(id1, is(not(id2)));
+ }
+
+ @Test
+ public void valueOf() {
+ IntentId id = new IntentId(0xdeadbeefL);
+ assertEquals("incorrect valueOf", id, IntentId.valueOf(0xdeadbeefL));
+ }
+
+ /**
+ * Tests the equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ final IntentId id1 = new IntentId(11111L);
+ final IntentId sameAsId1 = new IntentId(11111L);
+ final IntentId id2 = new IntentId(22222L);
+
+ new EqualsTester()
+ .addEqualityGroup(id1, sameAsId1)
+ .addEqualityGroup(id2)
+ .testEquals();
+ }
+
+ /**
+ * Tests construction of an IntentId object.
+ */
+ @Test
+ public void testConstruction() {
+ final IntentId id1 = new IntentId(987654321L);
+ assertEquals(id1.fingerprint(), 987654321L);
+
+ final IntentId emptyId = new IntentId();
+ assertEquals(emptyId.fingerprint(), 0L);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java
new file mode 100644
index 00000000..13786b4e
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * Test adapter for intent service.
+ */
+public class IntentServiceAdapter implements IntentService {
+ @Override
+ public void submit(Intent intent) {
+
+ }
+
+ @Override
+ public void withdraw(Intent intent) {
+
+ }
+
+ @Override
+ public void purge(Intent intent) {
+
+ }
+
+ @Override
+ public Iterable<Intent> getIntents() {
+ return null;
+ }
+
+ @Override
+ public Iterable<IntentData> getIntentData() {
+ return null;
+ }
+
+ @Override
+ public long getIntentCount() {
+ return 0;
+ }
+
+ @Override
+ public Intent getIntent(Key intentKey) {
+ return null;
+ }
+
+ @Override
+ public IntentState getIntentState(Key intentKey) {
+ return IntentState.INSTALLED;
+ }
+
+ @Override
+ public List<Intent> getInstallableIntents(Key intentKey) {
+ return null;
+ }
+
+ @Override
+ public boolean isLocal(Key intentKey) {
+ return false;
+ }
+
+ @Override
+ public Iterable<Intent> getPending() {
+ return null;
+ }
+
+ @Override
+ public void addListener(IntentListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(IntentListener listener) {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceTest.java
new file mode 100644
index 00000000..60857cac
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentServiceTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.resource.link.LinkResourceAllocations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.intent.IntentEvent.Type.*;
+
+/**
+ * Suite of tests for the intent service contract.
+ */
+public class IntentServiceTest {
+
+ public static final int IID = 123;
+ public static final int INSTALLABLE_IID = 234;
+
+ protected static final int GRACE_MS = 500; // millis
+
+ protected TestableIntentService service;
+ protected TestListener listener = new TestListener();
+ protected IdGenerator idGenerator = new MockIdGenerator();
+
+ @Before
+ public void setUp() {
+ service = createIntentService();
+ service.addListener(listener);
+ Intent.bindIdGenerator(idGenerator);
+ }
+
+ @After
+ public void tearDown() {
+ service.removeListener(listener);
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Creates a service instance appropriately instrumented for testing.
+ *
+ * @return testable intent service
+ */
+ protected TestableIntentService createIntentService() {
+ return new FakeIntentManager();
+ }
+
+ @Test
+ public void basics() {
+ // Make sure there are no intents
+ assertEquals("incorrect intent count", 0, service.getIntentCount());
+
+ // Register a compiler and an installer both setup for success.
+ service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
+
+ final Intent intent = new TestIntent(IID);
+ service.submit(intent);
+
+ // Allow a small window of time until the intent is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", IntentState.INSTALLED,
+ service.getIntentState(intent.key()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, INSTALL_REQ, INSTALLED);
+
+ // Make sure there is just one intent (and is ours)
+ assertEquals("incorrect intent count", 1, service.getIntentCount());
+
+ // Reset the listener events
+ listener.events.clear();
+
+ // Now withdraw the intent
+ service.withdraw(intent);
+
+ // Allow a small window of time until the event is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", IntentState.WITHDRAWN,
+ service.getIntentState(intent.key()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, WITHDRAWN);
+
+ // TODO: discuss what is the fate of intents after they have been withdrawn
+ // Make sure that the intent is no longer in the system
+// assertEquals("incorrect intent count", 0, service.getIntents().size());
+// assertNull("intent should not be found", service.getIntent(intent.id()));
+// assertNull("intent state should not be found", service.getIntentState(intent.id()));
+ }
+
+ @Test
+ public void failedCompilation() {
+ // Register a compiler programmed for success
+ service.registerCompiler(TestIntent.class, new TestCompiler(true));
+
+ // Submit an intent
+ final Intent intent = new TestIntent(IID);
+ service.submit(intent);
+
+ // Allow a small window of time until the intent is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", IntentState.FAILED,
+ service.getIntentState(intent.key()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, INSTALL_REQ, FAILED);
+ }
+
+ /**
+ * Validates that the test event listener has received the following events
+ * for the specified intent. Events received for other intents will not be
+ * considered.
+ *
+ * @param intent intent subject
+ * @param types list of event types for which events are expected
+ */
+ protected void validateEvents(Intent intent, IntentEvent.Type... types) {
+ Iterator<IntentEvent> events = listener.events.iterator();
+ for (IntentEvent.Type type : types) {
+ IntentEvent event = events.hasNext() ? events.next() : null;
+ if (event == null) {
+ fail("expected event not found: " + type);
+ } else if (intent.equals(event.subject())) {
+ assertEquals("incorrect state", type, event.type());
+ }
+ }
+
+ // Remainder of events should not apply to this intent; make sure.
+ while (events.hasNext()) {
+ assertFalse("unexpected event for intent",
+ intent.equals(events.next().subject()));
+ }
+ }
+
+ @Test
+ public void compilerBasics() {
+ // Make sure there are no compilers
+ assertEquals("incorrect compiler count", 0, service.getCompilers().size());
+
+ // Add a compiler and make sure that it appears in the map
+ IntentCompiler<TestIntent> compiler = new TestCompiler(false);
+ service.registerCompiler(TestIntent.class, compiler);
+ assertEquals("incorrect compiler", compiler,
+ service.getCompilers().get(TestIntent.class));
+
+ // Remove the same and make sure that it no longer appears in the map
+ service.unregisterCompiler(TestIntent.class);
+ assertNull("compiler should not be registered",
+ service.getCompilers().get(TestIntent.class));
+ }
+
+ @Test
+ public void implicitRegistration() {
+ // Add a compiler and make sure that it appears in the map
+ IntentCompiler<TestIntent> compiler = new TestCompiler(new TestSubclassInstallableIntent(INSTALLABLE_IID));
+ service.registerCompiler(TestIntent.class, compiler);
+ assertEquals("incorrect compiler", compiler,
+ service.getCompilers().get(TestIntent.class));
+
+ // Submit an intent which is a subclass of the one we registered
+ final Intent intent = new TestSubclassIntent(IID);
+ service.submit(intent);
+
+ // Allow some time for the intent to be compiled and installed
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", IntentState.INSTALLED,
+ service.getIntentState(intent.key()));
+ }
+ });
+
+ // Make sure that now we have an implicit registration of the compiler
+ // under the intent subclass
+ assertEquals("incorrect compiler", compiler,
+ service.getCompilers().get(TestSubclassIntent.class));
+
+ // TODO: discuss whether or if implicit registration should require implicit unregistration
+ // perhaps unregister by compiler or installer itself, rather than by class would be better
+ }
+
+
+ // Fixture to track emitted intent events
+ protected class TestListener implements IntentListener {
+ final List<IntentEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(IntentEvent event) {
+ events.add(event);
+ }
+ }
+
+ // Controllable compiler
+ private class TestCompiler implements IntentCompiler<TestIntent> {
+ private final boolean fail;
+ private final List<Intent> result;
+
+ TestCompiler(boolean fail) {
+ this.fail = fail;
+ this.result = Collections.emptyList();
+ }
+
+ TestCompiler(Intent... result) {
+ this.fail = false;
+ this.result = Arrays.asList(result);
+ }
+
+ @Override
+ public List<Intent> compile(TestIntent intent, List<Intent> installable,
+ Set<LinkResourceAllocations> resources) {
+ if (fail) {
+ throw new IntentException("compile failed by design");
+ }
+ List<Intent> compiled = new ArrayList<>(result);
+ return compiled;
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTest.java
new file mode 100644
index 00000000..d713b8aa
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Base facilities to test various intent tests.
+ */
+public abstract class IntentTest extends AbstractIntentTest {
+ /**
+ * Produces a set of items from the supplied items.
+ *
+ * @param items items to be placed in set
+ * @param <T> item type
+ * @return set of items
+ */
+ protected static <T> Set<T> itemSet(T[] items) {
+ return new HashSet<>(Arrays.asList(items));
+ }
+
+ /**
+ * Creates a new intent, but always a like intent, i.e. all instances will
+ * be equal, but should not be the same.
+ *
+ * @return intent
+ */
+ protected abstract Intent createOne();
+
+ /**
+ * Creates another intent, not equals to the one created by
+ * {@link #createOne()} and with a different hash code.
+ *
+ * @return another intent
+ */
+ protected abstract Intent createAnother();
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java
new file mode 100644
index 00000000..ac4ecff3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java
@@ -0,0 +1,496 @@
+/*
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+import org.onlab.util.Bandwidth;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleExtPayLoad;
+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.Criterion.Type;
+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.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.BandwidthResourceRequest;
+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.LinkResourceListener;
+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.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+import org.onosproject.net.topology.DefaultTopologyEdge;
+import org.onosproject.net.topology.DefaultTopologyVertex;
+import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.TopologyVertex;
+import org.onosproject.store.Timestamp;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.onosproject.net.NetTestTools.createPath;
+import static org.onosproject.net.NetTestTools.did;
+import static org.onosproject.net.NetTestTools.link;
+
+/**
+ * Common mocks used by the intent framework tests.
+ */
+public class IntentTestsMocks {
+ /**
+ * Mock traffic selector class used for satisfying API requirements.
+ */
+ public static class MockSelector implements TrafficSelector {
+ @Override
+ public Set<Criterion> criteria() {
+ return new HashSet<>();
+ }
+
+ @Override
+ public Criterion getCriterion(Type type) {
+ return null;
+ }
+ }
+
+ /**
+ * Mock traffic treatment class used for satisfying API requirements.
+ */
+ public static class MockTreatment implements TrafficTreatment {
+ @Override
+ public List<Instruction> deferred() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<Instruction> immediate() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<Instruction> allInstructions() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Instructions.TableTypeTransition tableTransition() {
+ return null;
+ }
+
+ @Override
+ public boolean clearedDeferred() {
+ return false;
+ }
+
+ @Override
+ public MetadataInstruction writeMetadata() {
+ return null;
+ }
+
+ @Override
+ public Instructions.MeterInstruction metered() {
+ return null;
+ }
+ }
+
+ /**
+ * Mock path service for creating paths within the test.
+ */
+ public static class MockPathService implements PathService {
+
+ final String[] pathHops;
+ final String[] reversePathHops;
+
+ /**
+ * Constructor that provides a set of hops to mock.
+ *
+ * @param pathHops path hops to mock
+ */
+ public MockPathService(String[] pathHops) {
+ this.pathHops = pathHops;
+ String[] reversed = pathHops.clone();
+ Collections.reverse(Arrays.asList(reversed));
+ reversePathHops = reversed;
+ }
+
+ @Override
+ public Set<Path> getPaths(ElementId src, ElementId dst) {
+ Set<Path> result = new HashSet<>();
+
+ String[] allHops = new String[pathHops.length];
+
+ if (src.toString().endsWith(pathHops[0])) {
+ System.arraycopy(pathHops, 0, allHops, 0, pathHops.length);
+ } else {
+ System.arraycopy(reversePathHops, 0, allHops, 0, pathHops.length);
+ }
+
+ result.add(createPath(allHops));
+ return result;
+ }
+
+ @Override
+ public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) {
+ final Set<Path> paths = getPaths(src, dst);
+
+ for (Path path : paths) {
+ final DeviceId srcDevice = path.src().deviceId();
+ final DeviceId dstDevice = path.dst().deviceId();
+ final TopologyVertex srcVertex = new DefaultTopologyVertex(srcDevice);
+ final TopologyVertex dstVertex = new DefaultTopologyVertex(dstDevice);
+ final Link link = link(src.toString(), 1, dst.toString(), 1);
+
+ final double weightValue = weight.weight(new DefaultTopologyEdge(srcVertex, dstVertex, link));
+ if (weightValue < 0) {
+ return new HashSet<>();
+ }
+ }
+ return paths;
+ }
+ }
+
+ public static class MockLinkResourceAllocations implements LinkResourceAllocations {
+ @Override
+ public Set<ResourceAllocation> getResourceAllocation(Link link) {
+ return ImmutableSet.of(
+ new LambdaResourceAllocation(LambdaResource.valueOf(77)),
+ new MplsLabelResourceAllocation(MplsLabel.valueOf(10)));
+ }
+
+ public IntentId intentId() {
+ return null;
+ }
+
+ public Collection<Link> links() {
+ return null;
+ }
+
+ public Set<ResourceRequest> resources() {
+ return null;
+ }
+
+ @Override
+ public ResourceType type() {
+ return null;
+ }
+ }
+
+ public static class MockedAllocationFailure extends RuntimeException { }
+
+ public static class MockResourceService implements LinkResourceService {
+
+ double availableBandwidth = -1.0;
+ int availableLambda = -1;
+
+ /**
+ * Allocates a resource service that will allow bandwidth allocations
+ * up to a limit.
+ *
+ * @param bandwidth available bandwidth limit
+ * @return resource manager for bandwidth requests
+ */
+ public static MockResourceService makeBandwidthResourceService(double bandwidth) {
+ final MockResourceService result = new MockResourceService();
+ result.availableBandwidth = bandwidth;
+ return result;
+ }
+
+ /**
+ * Allocates a resource service that will allow lambda allocations.
+ *
+ * @param lambda Lambda to return for allocation requests. Currently unused
+ * @return resource manager for lambda requests
+ */
+ public static MockResourceService makeLambdaResourceService(int lambda) {
+ final MockResourceService result = new MockResourceService();
+ result.availableLambda = lambda;
+ return result;
+ }
+
+ public void setAvailableBandwidth(double availableBandwidth) {
+ this.availableBandwidth = availableBandwidth;
+ }
+
+ public void setAvailableLambda(int availableLambda) {
+ this.availableLambda = availableLambda;
+ }
+
+
+ @Override
+ public LinkResourceAllocations requestResources(LinkResourceRequest req) {
+ int lambda = -1;
+ double bandwidth = -1.0;
+
+ for (ResourceRequest resourceRequest : req.resources()) {
+ if (resourceRequest.type() == ResourceType.BANDWIDTH) {
+ final BandwidthResourceRequest brr = (BandwidthResourceRequest) resourceRequest;
+ bandwidth = brr.bandwidth().toDouble();
+ } else if (resourceRequest.type() == ResourceType.LAMBDA) {
+ lambda = 1;
+ }
+ }
+
+ if (availableBandwidth < bandwidth) {
+ throw new MockedAllocationFailure();
+ }
+ if (lambda > 0 && availableLambda == 0) {
+ throw new MockedAllocationFailure();
+ }
+
+ return new IntentTestsMocks.MockLinkResourceAllocations();
+ }
+
+ @Override
+ public void releaseResources(LinkResourceAllocations allocations) {
+ // Mock
+ }
+
+ @Override
+ public LinkResourceAllocations updateResources(LinkResourceRequest req,
+ LinkResourceAllocations oldAllocations) {
+ return null;
+ }
+
+ @Override
+ public Iterable<LinkResourceAllocations> getAllocations() {
+ return ImmutableSet.of(
+ new IntentTestsMocks.MockLinkResourceAllocations());
+ }
+
+ @Override
+ public Iterable<LinkResourceAllocations> getAllocations(Link link) {
+ return ImmutableSet.of(
+ new IntentTestsMocks.MockLinkResourceAllocations());
+ }
+
+ @Override
+ public LinkResourceAllocations getAllocations(IntentId intentId) {
+ return new IntentTestsMocks.MockLinkResourceAllocations();
+ }
+
+ @Override
+ public Iterable<ResourceRequest> getAvailableResources(Link link) {
+ final List<ResourceRequest> result = new LinkedList<>();
+ if (availableBandwidth > 0.0) {
+ result.add(new BandwidthResourceRequest(
+ new BandwidthResource(Bandwidth.bps(availableBandwidth))));
+ }
+ if (availableLambda > 0) {
+ result.add(new LambdaResourceRequest());
+ }
+ return result;
+ }
+
+ @Override
+ public Iterable<ResourceRequest> getAvailableResources(Link link, LinkResourceAllocations allocations) {
+ return null;
+ }
+
+ @Override
+ public void addListener(LinkResourceListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(LinkResourceListener listener) {
+
+ }
+ }
+
+ private static final IntentTestsMocks.MockSelector SELECTOR =
+ new IntentTestsMocks.MockSelector();
+ private static final IntentTestsMocks.MockTreatment TREATMENT =
+ new IntentTestsMocks.MockTreatment();
+
+ public static class MockFlowRule implements FlowRule {
+ static int nextId = 0;
+
+ int priority;
+ int tableId;
+ long timestamp;
+ int id;
+ FlowRuleExtPayLoad payLoad;
+
+ public MockFlowRule(int priority) {
+ this.priority = priority;
+ this.tableId = 0;
+ this.timestamp = System.currentTimeMillis();
+ this.id = nextId++;
+ this.payLoad = null;
+ }
+
+ public MockFlowRule(int priority, FlowRuleExtPayLoad payLoad) {
+ this.priority = priority;
+ this.timestamp = System.currentTimeMillis();
+ this.id = nextId++;
+ this.payLoad = payLoad;
+ }
+
+ @Override
+ public FlowId id() {
+ return FlowId.valueOf(id);
+ }
+
+ @Override
+ public short appId() {
+ return 0;
+ }
+
+ @Override
+ public GroupId groupId() {
+ return new DefaultGroupId(0);
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return did("1");
+ }
+
+ @Override
+ public TrafficSelector selector() {
+ return SELECTOR;
+ }
+
+ @Override
+ public TrafficTreatment treatment() {
+ return TREATMENT;
+ }
+
+ @Override
+ public int timeout() {
+ return 0;
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(priority);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final MockFlowRule other = (MockFlowRule) obj;
+ return Objects.equals(this.timestamp, other.timestamp) &&
+ this.id == other.id;
+ }
+
+ @Override
+ public boolean exactMatch(FlowRule rule) {
+ return this.equals(rule);
+ }
+
+ @Override
+ public int tableId() {
+ return tableId;
+ }
+
+ @Override
+ public FlowRuleExtPayLoad payLoad() {
+ return payLoad;
+ }
+ }
+
+ public static class MockIntent extends Intent {
+ private static AtomicLong counter = new AtomicLong(0);
+
+ private final Long number;
+
+ public MockIntent(Long number) {
+ super(NetTestTools.APP_ID, null, Collections.emptyList(),
+ Intent.DEFAULT_INTENT_PRIORITY);
+ this.number = number;
+ }
+
+ public MockIntent(Long number, Collection<NetworkResource> resources) {
+ super(NetTestTools.APP_ID, null, resources, Intent.DEFAULT_INTENT_PRIORITY);
+ this.number = number;
+ }
+
+ public Long number() {
+ return number;
+ }
+
+ public static Long nextId() {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("appId", appId())
+ .toString();
+ }
+ }
+
+ public static class MockTimestamp implements Timestamp {
+ final int value;
+
+ public MockTimestamp(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ if (!(o instanceof MockTimestamp)) {
+ return -1;
+ }
+ MockTimestamp that = (MockTimestamp) o;
+ return this.value - that.value;
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/KeyTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/KeyTest.java
new file mode 100644
index 00000000..dfc73442
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/KeyTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.net.NetTestTools;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for the intent key class.
+ */
+public class KeyTest {
+
+ private static final String KEY_1 = "key1";
+ private static final String KEY_2 = "key2";
+ private static final String KEY_3 = "key3";
+
+ private static final long LONG_KEY_1 = 0x1111;
+ private static final long LONG_KEY_2 = 0x2222;
+ private static final long LONG_KEY_3 = 0x3333;
+
+ /**
+ * Tests that keys are properly immutable.
+ */
+ @Test
+ public void keysAreImmutable() {
+ assertThatClassIsImmutableBaseClass(Key.class);
+
+ // Will be a long based key, class is private so cannot be
+ // accessed directly
+ Key longKey = Key.of(0xabcdefL, NetTestTools.APP_ID);
+ assertThatClassIsImmutable(longKey.getClass());
+
+ // Will be a String based key, class is private so cannot be
+ // accessed directly.
+ Key stringKey = Key.of("some key", NetTestTools.APP_ID);
+ assertThatClassIsImmutable(stringKey.getClass());
+ }
+
+ /**
+ * Tests string key construction.
+ */
+ @Test
+ public void stringKeyConstruction() {
+ Key stringKey1 = Key.of(KEY_3, NetTestTools.APP_ID);
+ assertThat(stringKey1, notNullValue());
+ Key stringKey2 = Key.of(KEY_3, NetTestTools.APP_ID);
+ assertThat(stringKey2, notNullValue());
+
+ assertThat(stringKey1.hash(), is(stringKey2.hash()));
+ }
+
+ /**
+ * Tests long key construction.
+ */
+ @Test
+ public void longKeyConstruction() {
+ Key longKey1 = Key.of(LONG_KEY_3, NetTestTools.APP_ID);
+ assertThat(longKey1, notNullValue());
+ Key longKey2 = Key.of(LONG_KEY_3, NetTestTools.APP_ID);
+ assertThat(longKey2, notNullValue());
+
+ assertThat(longKey1.hash(), is(longKey2.hash()));
+ }
+
+ /**
+ * Tests equals for string based keys.
+ */
+ @Test
+ public void stringKey() {
+ Key stringKey1 = Key.of(KEY_1, NetTestTools.APP_ID);
+ Key copyOfStringKey1 = Key.of(KEY_1, NetTestTools.APP_ID);
+ Key stringKey2 = Key.of(KEY_2, NetTestTools.APP_ID);
+ Key copyOfStringKey2 = Key.of(KEY_2, NetTestTools.APP_ID);
+ Key stringKey3 = Key.of(KEY_3, NetTestTools.APP_ID);
+
+ new EqualsTester()
+ .addEqualityGroup(stringKey1, copyOfStringKey1)
+ .addEqualityGroup(stringKey2, copyOfStringKey2)
+ .addEqualityGroup(stringKey3)
+ .testEquals();
+ }
+
+ /**
+ * Tests equals for long based keys.
+ */
+ @Test
+ public void longKey() {
+ Key longKey1 = Key.of(LONG_KEY_1, NetTestTools.APP_ID);
+ Key copyOfLongKey1 = Key.of(LONG_KEY_1, NetTestTools.APP_ID);
+ Key longKey2 = Key.of(LONG_KEY_2, NetTestTools.APP_ID);
+ Key copyOfLongKey2 = Key.of(LONG_KEY_2, NetTestTools.APP_ID);
+ Key longKey3 = Key.of(LONG_KEY_3, NetTestTools.APP_ID);
+
+ new EqualsTester()
+ .addEqualityGroup(longKey1, copyOfLongKey1)
+ .addEqualityGroup(longKey2, copyOfLongKey2)
+ .addEqualityGroup(longKey3)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/LinkCollectionIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/LinkCollectionIntentTest.java
new file mode 100644
index 00000000..88fa7f4f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/LinkCollectionIntentTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.resource.link.LambdaResource;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.startsWith;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.link;
+
+/**
+ * Unit tests for the LinkCollectionIntent class.
+ */
+public class LinkCollectionIntentTest extends IntentTest {
+
+ final ConnectPoint ingress = NetTestTools.connectPoint("ingress", 2);
+ final ConnectPoint egress = NetTestTools.connectPoint("egress", 3);
+ final TrafficSelector selector = new IntentTestsMocks.MockSelector();
+ final IntentTestsMocks.MockTreatment treatment = new IntentTestsMocks.MockTreatment();
+
+ /**
+ * Checks that the LinkCollectionIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(LinkCollectionIntent.class);
+ }
+
+ /**
+ * Tests equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+
+ final HashSet<Link> links1 = new HashSet<>();
+ links1.add(link("src", 1, "dst", 2));
+ final LinkCollectionIntent collectionIntent1 =
+ LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links1)
+ .ingressPoints(ImmutableSet.of(ingress))
+ .egressPoints(ImmutableSet.of(egress))
+ .build();
+
+ final HashSet<Link> links2 = new HashSet<>();
+ links2.add(link("src", 1, "dst", 3));
+ final LinkCollectionIntent collectionIntent2 =
+ LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links2)
+ .ingressPoints(ImmutableSet.of(ingress))
+ .egressPoints(ImmutableSet.of(egress))
+ .build();
+
+ new EqualsTester()
+ .addEqualityGroup(collectionIntent1)
+ .addEqualityGroup(collectionIntent2)
+ .testEquals();
+ }
+
+ /**
+ * Tests constructor without constraints.
+ */
+ @Test
+ public void testConstructor() {
+ final HashSet<Link> links1 = new HashSet<>();
+ links1.add(link("src", 1, "dst", 2));
+ final LinkCollectionIntent collectionIntent =
+ LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links1)
+ .ingressPoints(ImmutableSet.of(ingress))
+ .egressPoints(ImmutableSet.of(egress))
+ .build();
+
+ final Set<Link> createdLinks = collectionIntent.links();
+ assertThat(createdLinks, hasSize(1));
+ assertThat(collectionIntent.isInstallable(), is(false));
+ assertThat(collectionIntent.treatment(), is(treatment));
+ assertThat(collectionIntent.selector(), is(selector));
+ assertThat(collectionIntent.ingressPoints(), is(ImmutableSet.of(ingress)));
+ assertThat(collectionIntent.egressPoints(), is(ImmutableSet.of(egress)));
+ assertThat(collectionIntent.resources(), hasSize(1));
+ final List<Constraint> createdConstraints = collectionIntent.constraints();
+ assertThat(createdConstraints, hasSize(0));
+ }
+
+ /**
+ * Tests constructor with constraints.
+ */
+ @Test
+ public void testConstructorWithConstraints() {
+ final HashSet<Link> links1 = new HashSet<>();
+ final LinkedList<Constraint> constraints = new LinkedList<>();
+
+ links1.add(link("src", 1, "dst", 2));
+ constraints.add(new LambdaConstraint(LambdaResource.valueOf(23)));
+ final LinkCollectionIntent collectionIntent =
+ LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links1)
+ .ingressPoints(ImmutableSet.of(ingress))
+ .egressPoints(ImmutableSet.of(egress))
+ .constraints(constraints)
+ .priority(8888)
+ .build();
+
+ final Set<Link> createdLinks = collectionIntent.links();
+ assertThat(createdLinks, hasSize(1));
+ assertThat(collectionIntent.isInstallable(), is(false));
+ assertThat(collectionIntent.treatment(), is(treatment));
+ assertThat(collectionIntent.selector(), is(selector));
+ assertThat(collectionIntent.ingressPoints(), is(ImmutableSet.of(ingress)));
+ assertThat(collectionIntent.egressPoints(), is(ImmutableSet.of(egress)));
+
+ final List<Constraint> createdConstraints = collectionIntent.constraints();
+ assertThat(createdConstraints, hasSize(1));
+ assertThat(createdConstraints.get(0).toString(), startsWith("LambdaConstraint"));
+ }
+
+ /**
+ * Tests constructor with constraints.
+ */
+ @Test
+ public void testSerializerConstructor() {
+
+ final LinkCollectionIntent collectionIntent =
+ new LinkCollectionIntent();
+
+ final Set<Link> createdLinks = collectionIntent.links();
+ assertThat(createdLinks, nullValue());
+ assertThat(collectionIntent.isInstallable(), is(false));
+ assertThat(collectionIntent.treatment(), nullValue());
+ assertThat(collectionIntent.selector(), nullValue());
+ assertThat(collectionIntent.ingressPoints(), nullValue());
+ assertThat(collectionIntent.egressPoints(), nullValue());
+
+ final List<Constraint> createdConstraints = collectionIntent.constraints();
+ assertThat(createdConstraints, hasSize(0));
+ }
+
+ @Override
+ protected Intent createOne() {
+ HashSet<Link> links1 = new HashSet<>();
+ links1.add(link("src", 1, "dst", 2));
+ return LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links1)
+ .ingressPoints(ImmutableSet.of(ingress))
+ .egressPoints(ImmutableSet.of(egress))
+ .build();
+ }
+
+ @Override
+ protected Intent createAnother() {
+ HashSet<Link> links2 = new HashSet<>();
+ links2.add(link("src", 1, "dst", 3));
+ return LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(links2)
+ .ingressPoints(ImmutableSet.of(ingress))
+ .egressPoints(ImmutableSet.of(egress))
+ .build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MockIdGenerator.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MockIdGenerator.java
new file mode 100644
index 00000000..5e84cd8c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MockIdGenerator.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.onosproject.core.IdGenerator;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Mock id generator for testing.
+ */
+public class MockIdGenerator implements IdGenerator {
+ private AtomicLong nextId = new AtomicLong(0);
+
+ @Override
+ public long getNewId() {
+ return nextId.getAndIncrement();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsIntentTest.java
new file mode 100644
index 00000000..196d6ad4
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsIntentTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+
+/**
+ * Unit tests for the MplsIntent class.
+ */
+
+public class MplsIntentTest extends AbstractIntentTest {
+ static final int PRIORITY = 22;
+
+ MplsIntent intent1;
+ MplsIntent intent2;
+
+ Optional<MplsLabel> label1;
+ Optional<MplsLabel> label2;
+
+ TrafficSelector selector;
+ TrafficTreatment treatment;
+
+ @Before
+ public void mplsIntentTestSetUp() throws Exception {
+
+ label1 = Optional.of(MplsLabel.mplsLabel(1));
+ label2 = Optional.of(MplsLabel.mplsLabel(2));
+
+ selector = new IntentTestsMocks.MockSelector();
+ treatment = new IntentTestsMocks.MockTreatment();
+
+ intent1 = MplsIntent.builder()
+ .appId(APP_ID)
+ .ingressLabel(label1)
+ .egressLabel(label2)
+ .ingressPoint(connectPoint("in", 1))
+ .egressPoint(connectPoint("out", 1))
+ .selector(selector)
+ .treatment(treatment)
+ .priority(PRIORITY)
+ .build();
+
+ intent2 = MplsIntent.builder()
+ .appId(APP_ID)
+ .ingressLabel(label1)
+ .egressLabel(label2)
+ .ingressPoint(connectPoint("in", 2))
+ .egressPoint(connectPoint("out", 2))
+ .selector(selector)
+ .treatment(treatment)
+ .priority(PRIORITY)
+ .build();
+ }
+
+ /**
+ * Checks that the MplsIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(MplsIntent.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(intent1)
+ .addEqualityGroup(intent2)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the MplsIntent objects are created correctly.
+ */
+ @Test
+ public void testContents() {
+ assertThat(intent1.appId(), equalTo(APP_ID));
+ assertThat(intent1.ingressLabel(), equalTo(label1));
+ assertThat(intent1.egressLabel(), equalTo(label2));
+ assertThat(intent1.ingressPoint(), equalTo(connectPoint("in", 1)));
+ assertThat(intent1.egressPoint(), equalTo(connectPoint("out", 1)));
+ assertThat(intent1.selector(), equalTo(intent2.selector()));
+ assertThat(intent1.treatment(), equalTo(intent2.treatment()));
+ assertThat(intent1.priority(), is(PRIORITY));
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsPathIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsPathIntentTest.java
new file mode 100644
index 00000000..551f19eb
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MplsPathIntentTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.net.Path;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.createPath;
+
+/**
+ * Unit tests for the MplsPathIntent class.
+ */
+public class MplsPathIntentTest extends AbstractIntentTest {
+
+ static final int PRIORITY = 777;
+
+ MplsPathIntent intent1;
+ MplsPathIntent intent2;
+ Path defaultPath;
+ Optional<MplsLabel> label1;
+ Optional<MplsLabel> label2;
+ TrafficSelector selector;
+ TrafficTreatment treatment;
+
+ @Before
+ public void mplsPathIntentTestSetUp() {
+ defaultPath = createPath("a", "b", "c");
+ selector = new IntentTestsMocks.MockSelector();
+ treatment = new IntentTestsMocks.MockTreatment();
+
+ label1 = Optional.of(MplsLabel.mplsLabel(1));
+ label2 = Optional.of(MplsLabel.mplsLabel(2));
+ intent1 = MplsPathIntent.builder()
+ .appId(APP_ID)
+ .ingressLabel(label1)
+ .egressLabel(label2)
+ .path(defaultPath)
+ .priority(PRIORITY)
+ .build();
+
+ intent2 = MplsPathIntent.builder()
+ .appId(APP_ID)
+ .ingressLabel(label1)
+ .egressLabel(label2)
+ .path(defaultPath)
+ .priority(PRIORITY)
+ .build();
+ }
+
+
+ /**
+ * Checks that the MplsPathIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(MplsPathIntent.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(intent1)
+ .addEqualityGroup(intent2)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the MPLS path intent objects are created correctly.
+ */
+ @Test
+ public void testContents() {
+ assertThat(intent1.appId(), equalTo(APP_ID));
+ assertThat(intent1.ingressLabel(), equalTo(label1));
+ assertThat(intent1.egressLabel(), equalTo(label2));
+ assertThat(intent1.selector(), equalTo(intent2.selector()));
+ assertThat(intent1.treatment(), equalTo(intent2.treatment()));
+ assertThat(intent1.priority(), is(PRIORITY));
+ assertThat(intent1.path(), is(defaultPath));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MultiPointToSinglePointIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MultiPointToSinglePointIntentTest.java
new file mode 100644
index 00000000..00be3101
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/MultiPointToSinglePointIntentTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Suite of tests of the multi-to-single point intent descriptor.
+ */
+public class MultiPointToSinglePointIntentTest extends ConnectivityIntentTest {
+
+ /**
+ * Checks that the MultiPointToSinglePointIntent class is immutable.
+ */
+ @Test
+ public void checkImmutability() {
+ assertThatClassIsImmutable(MultiPointToSinglePointIntent.class);
+ }
+
+ @Test
+ public void basics() {
+ MultiPointToSinglePointIntent intent = createOne();
+ assertEquals("incorrect id", APPID, intent.appId());
+ assertEquals("incorrect match", MATCH, intent.selector());
+ assertEquals("incorrect ingress", PS1, intent.ingressPoints());
+ assertEquals("incorrect egress", P2, intent.egressPoint());
+ }
+
+ @Override
+ protected MultiPointToSinglePointIntent createOne() {
+ return MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .ingressPoints(PS1)
+ .egressPoint(P2)
+ .build();
+ }
+
+ @Override
+ protected MultiPointToSinglePointIntent createAnother() {
+ return MultiPointToSinglePointIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .ingressPoints(PS2)
+ .egressPoint(P1)
+ .build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalConnectivityIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalConnectivityIntentTest.java
new file mode 100644
index 00000000..7df220c3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalConnectivityIntentTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for the OpticalConnectivityIntent class.
+ */
+public class OpticalConnectivityIntentTest {
+
+ /**
+ * Checks that the HostToHostIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(OpticalConnectivityIntent.class);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalPathIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalPathIntentTest.java
new file mode 100644
index 00000000..36e018a9
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/OpticalPathIntentTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.Path;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+import static org.onosproject.net.NetTestTools.createLambda;
+import static org.onosproject.net.NetTestTools.createPath;
+
+public class OpticalPathIntentTest extends AbstractIntentTest {
+
+ static final int PRIORITY = 777;
+
+ OpticalPathIntent intent1;
+ OpticalPathIntent intent2;
+ Path defaultPath;
+
+ @Before
+ public void opticalPathIntentTestSetUp() {
+ defaultPath = createPath("a", "b", "c");
+ intent1 = OpticalPathIntent.builder()
+ .appId(APP_ID)
+ .src(connectPoint("one", 1))
+ .dst(connectPoint("two", 2))
+ .path(defaultPath)
+ .lambda(createLambda())
+ .signalType(OchSignalType.FIXED_GRID)
+ .priority(PRIORITY)
+ .build();
+
+ intent2 = OpticalPathIntent.builder()
+ .appId(APP_ID)
+ .src(connectPoint("two", 1))
+ .dst(connectPoint("one", 2))
+ .path(defaultPath)
+ .lambda(createLambda())
+ .signalType(OchSignalType.FIXED_GRID)
+ .priority(PRIORITY)
+ .build();
+ }
+
+ /**
+ * Checks that the OpticalPathIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(OpticalPathIntent.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(intent1)
+ .addEqualityGroup(intent2)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the optical path intent objects are created correctly.
+ */
+ @Test
+ public void testContents() {
+ assertThat(intent1.appId(), equalTo(APP_ID));
+ assertThat(intent1.src(), Matchers.equalTo(connectPoint("one", 1)));
+ assertThat(intent1.dst(), Matchers.equalTo(connectPoint("two", 2)));
+ assertThat(intent1.priority(), is(PRIORITY));
+ assertThat(intent1.path(), is(defaultPath));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PartitionServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PartitionServiceAdapter.java
new file mode 100644
index 00000000..ffb2635e
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PartitionServiceAdapter.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.onosproject.cluster.NodeId;
+
+import static org.junit.Assert.*;
+
+/**
+ * Testing adapter for the partition service.
+ */
+public class PartitionServiceAdapter implements PartitionService {
+ @Override
+ public boolean isMine(Key intentKey) {
+ return true;
+ }
+
+ @Override
+ public NodeId getLeader(Key intentKey) {
+ return null;
+ }
+
+ @Override
+ public void addListener(PartitionEventListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(PartitionEventListener listener) {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PathIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PathIntentTest.java
new file mode 100644
index 00000000..dfbc1846
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PathIntentTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.PortNumber.portNumber;
+
+public class PathIntentTest extends ConnectivityIntentTest {
+ // 111:11 --> 222:22
+ private static final Path PATH1 = NetTestTools.createPath("111", "222");
+
+ // 111:11 --> 333:33
+ private static final Path PATH2 = NetTestTools.createPath("222", "333");
+
+ private final ProviderId provider1 = new ProviderId("of", "1");
+ private final DeviceId device1 = deviceId("1");
+ private final DeviceId device2 = deviceId("2");
+ private final PortNumber port1 = portNumber(1);
+ private final PortNumber port2 = portNumber(2);
+ private final PortNumber port3 = portNumber(3);
+ private final PortNumber port4 = portNumber(4);
+ private final ConnectPoint cp1 = new ConnectPoint(device1, port1);
+ private final ConnectPoint cp2 = new ConnectPoint(device1, port2);
+ private final ConnectPoint cp3 = new ConnectPoint(device2, port3);
+ private final ConnectPoint cp4 = new ConnectPoint(device2, port4);
+ private final DefaultLink link1 = new DefaultLink(provider1, cp1, cp2, DIRECT);
+ private final DefaultLink link2 = new DefaultLink(provider1, cp1, cp2, DIRECT);
+ private final double cost = 1;
+
+ @Test
+ public void basics() {
+ PathIntent intent = createOne();
+ assertEquals("incorrect id", APPID, intent.appId());
+ assertEquals("incorrect match", MATCH, intent.selector());
+ assertEquals("incorrect action", NOP, intent.treatment());
+ assertEquals("incorrect path", PATH1, intent.path());
+ }
+
+ @Override
+ protected PathIntent createOne() {
+ return PathIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .path(PATH1)
+ .build();
+ }
+
+ @Override
+ protected PathIntent createAnother() {
+ return PathIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .path(PATH2)
+ .build();
+ }
+
+ /**
+ * Tests the constructor raises IllegalArgumentException when the same device is specified in
+ * source and destination of a link.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRaiseExceptionWhenSameDevices() {
+ PathIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .path(new DefaultPath(provider1, Collections.singletonList(link1), cost))
+ .build();
+ }
+
+ /**
+ * Tests the constructor raises IllegalArgumentException when the different elements are specified
+ * in source element of the first link and destination element of the second link.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRaiseExceptionWhenDifferentDevice() {
+ PathIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .path(new DefaultPath(provider1, Arrays.asList(link1, link2), cost))
+ .build();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
new file mode 100644
index 00000000..46772f9f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+
+/**
+ * Suite of tests of the point-to-point intent descriptor.
+ */
+public class PointToPointIntentTest extends ConnectivityIntentTest {
+
+ /**
+ * Checks that the MultiPointToSinglePointIntent class is immutable.
+ */
+ @Test
+ public void checkImmutability() {
+ assertThatClassIsImmutableBaseClass(PointToPointIntent.class);
+ }
+
+ @Test
+ public void basics() {
+ PointToPointIntent intent = createOne();
+ assertEquals("incorrect id", APPID, intent.appId());
+ assertEquals("incorrect match", MATCH, intent.selector());
+ assertEquals("incorrect ingress", P1, intent.ingressPoint());
+ assertEquals("incorrect egress", P2, intent.egressPoint());
+ }
+
+ @Override
+ protected PointToPointIntent createOne() {
+ return PointToPointIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .ingressPoint(P1)
+ .egressPoint(P2)
+ .build();
+ }
+
+ @Override
+ protected PointToPointIntent createAnother() {
+ return PointToPointIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .ingressPoint(P2)
+ .egressPoint(P1)
+ .build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java
new file mode 100644
index 00000000..18c6d7bc
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Suite of tests of the single-to-multi point intent descriptor.
+ */
+public class SinglePointToMultiPointIntentTest extends ConnectivityIntentTest {
+
+ /**
+ * Checks that the SinglePointToMultiPointIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(SinglePointToMultiPointIntent.class);
+ }
+
+ @Test
+ public void basics() {
+ SinglePointToMultiPointIntent intent = createOne();
+ assertEquals("incorrect id", APPID, intent.appId());
+ assertEquals("incorrect match", MATCH, intent.selector());
+ assertEquals("incorrect ingress", P1, intent.ingressPoint());
+ assertEquals("incorrect egress", PS2, intent.egressPoints());
+ }
+
+ @Override
+ protected SinglePointToMultiPointIntent createOne() {
+ return SinglePointToMultiPointIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .ingressPoint(P1)
+ .egressPoints(PS2)
+ .build();
+ }
+
+ @Override
+ protected SinglePointToMultiPointIntent createAnother() {
+ return SinglePointToMultiPointIntent.builder()
+ .appId(APPID)
+ .selector(MATCH)
+ .treatment(NOP)
+ .ingressPoint(P2)
+ .egressPoints(PS1)
+ .build();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestInstallableIntent.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestInstallableIntent.java
new file mode 100644
index 00000000..d949ee16
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestInstallableIntent.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import org.onosproject.TestApplicationId;
+
+import java.util.Collections;
+
+/**
+ * An installable intent used in the unit test.
+ */
+public class TestInstallableIntent extends Intent {
+
+ private final int value;
+
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param value intent ID
+ */
+ public TestInstallableIntent(int value) { // FIXME
+ super(new TestApplicationId("foo"), null, Collections.emptyList(),
+ Intent.DEFAULT_INTENT_PRIORITY);
+ this.value = value;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestInstallableIntent() {
+ super();
+ value = -1;
+ }
+
+ @Override
+ public boolean isInstallable() {
+ return true;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestIntent.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestIntent.java
new file mode 100644
index 00000000..857fc8b5
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestIntent.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import org.onosproject.TestApplicationId;
+
+import java.util.Collections;
+
+/**
+ * An intent used in the unit test.
+ */
+public class TestIntent extends Intent {
+
+ private final int value;
+
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param value intent ID
+ */
+ public TestIntent(int value) { // FIXME
+ super(new TestApplicationId("foo"), null, Collections.emptyList(),
+ Intent.DEFAULT_INTENT_PRIORITY);
+ this.value = value;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestIntent() {
+ super();
+ value = -1;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassInstallableIntent.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassInstallableIntent.java
new file mode 100644
index 00000000..82c14843
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassInstallableIntent.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;
+
+/**
+ * An intent used in the unit test.
+ */
+public class TestSubclassInstallableIntent extends TestInstallableIntent {
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param id intent ID
+ */
+ public TestSubclassInstallableIntent(int id) { //FIXME
+ super(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestSubclassInstallableIntent() {
+ super();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassIntent.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassIntent.java
new file mode 100644
index 00000000..44c81189
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestSubclassIntent.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;
+
+/**
+ * An intent used in the unit test.
+ */
+public class TestSubclassIntent extends TestIntent {
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param id intent ID
+ */
+ public TestSubclassIntent(int id) { //FIXME
+ super(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestSubclassIntent() {
+ super();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestTools.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestTools.java
new file mode 100644
index 00000000..80ae180d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestTools.java
@@ -0,0 +1,141 @@
+/*
+ * 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 static org.junit.Assert.fail;
+
+/**
+ * Set of test tools.
+ */
+public final class TestTools {
+
+ // Disallow construction
+ private TestTools() {
+ }
+
+ /**
+ * Utility method to pause the current thread for the specified number of
+ * milliseconds.
+ *
+ * @param ms number of milliseconds to pause
+ */
+ public static void delay(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ fail("unexpected interrupt");
+ }
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ *
+ * @param start start time, in millis since start of epoch from which the
+ * duration will be measured
+ * @param delay initial delay (in milliseconds) before the first assertion
+ * attempt
+ * @param step delay (in milliseconds) between successive assertion
+ * attempts
+ * @param duration number of milliseconds beyond the given start time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(long start, int delay, int step,
+ int duration, Runnable assertions) {
+ delay(delay);
+ while (true) {
+ try {
+ assertions.run();
+ break;
+ } catch (AssertionError e) {
+ if (System.currentTimeMillis() - start > duration) {
+ throw e;
+ }
+ }
+ delay(step);
+ }
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ * <p>
+ * The start of the period is the current time.
+ *
+ * @param delay initial delay (in milliseconds) before the first assertion
+ * attempt
+ * @param step delay (in milliseconds) between successive assertion
+ * attempts
+ * @param duration number of milliseconds beyond the current time time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(int delay, int step, int duration,
+ Runnable assertions) {
+ assertAfter(System.currentTimeMillis(), delay, step, duration,
+ assertions);
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ * <p>
+ * The start of the period is the current time and the first assertion
+ * attempt is delayed by the value of {@code step} parameter.
+ *
+ * @param step delay (in milliseconds) between successive assertion
+ * attempts
+ * @param duration number of milliseconds beyond the current time time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(int step, int duration,
+ Runnable assertions) {
+ assertAfter(step, step, duration, assertions);
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ * <p>
+ * The start of the period is the current time and each successive
+ * assertion attempt is delayed by at least 10 milliseconds unless the
+ * {@code duration} is less than that, in which case the one and only
+ * assertion is made after that delay.
+ *
+ * @param duration number of milliseconds beyond the current time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(int duration, Runnable assertions) {
+ int step = Math.min(duration, Math.max(10, duration / 10));
+ assertAfter(step, duration, assertions);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestableIntentService.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestableIntentService.java
new file mode 100644
index 00000000..792c0ff8
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TestableIntentService.java
@@ -0,0 +1,27 @@
+/*
+ * 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.List;
+
+/**
+ * Abstraction of an extensible intent service enabled for unit tests.
+ */
+public interface TestableIntentService extends IntentService, IntentExtensionService {
+
+ List<IntentException> getExceptions();
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TwoWayP2PIntentTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TwoWayP2PIntentTest.java
new file mode 100644
index 00000000..0986216a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/TwoWayP2PIntentTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.connectPoint;
+/**
+ * Unit tests for the TwoWayP2PIntent class.
+ */
+public class TwoWayP2PIntentTest extends AbstractIntentTest {
+
+ TrafficSelector selector;
+ TrafficTreatment treatment;
+
+ TwoWayP2PIntent intent1;
+ TwoWayP2PIntent intent2;
+
+ static final int PRIORITY = 12;
+
+ @Before
+ public void twoWatP2PIntentTestSetUp() {
+ selector = new IntentTestsMocks.MockSelector();
+ treatment = new IntentTestsMocks.MockTreatment();
+
+ intent1 = TwoWayP2PIntent.builder()
+ .appId(APP_ID)
+ .priority(PRIORITY)
+ .selector(selector)
+ .treatment(treatment)
+ .one(connectPoint("one", 1))
+ .two(connectPoint("two", 2))
+ .build();
+
+ intent2 = TwoWayP2PIntent.builder()
+ .appId(APP_ID)
+ .priority(PRIORITY)
+ .selector(selector)
+ .treatment(treatment)
+ .one(connectPoint("two", 2))
+ .two(connectPoint("three", 2))
+ .build();
+ }
+
+ /**
+ * Checks that the TwoWayP2PIntent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(TwoWayP2PIntent.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(intent1)
+ .addEqualityGroup(intent2)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the optical path ntent objects are created correctly.
+ */
+ @Test
+ public void testContents() {
+ assertThat(intent1.appId(), equalTo(APP_ID));
+ assertThat(intent1.one(), Matchers.equalTo(connectPoint("one", 1)));
+ assertThat(intent1.two(), Matchers.equalTo(connectPoint("two", 2)));
+ assertThat(intent1.priority(), is(PRIORITY));
+ assertThat(intent1.selector(), is(selector));
+ assertThat(intent1.treatment(), is(treatment));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/AnnotationConstraintTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/AnnotationConstraintTest.java
new file mode 100644
index 00000000..b87dc12b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/AnnotationConstraintTest.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.constraint;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import static org.easymock.EasyMock.createMock;
+import static org.hamcrest.Matchers.closeTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.net.DefaultLinkTest.cp;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test for link annotated value threshold.
+ */
+public class AnnotationConstraintTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final DeviceId DID1 = deviceId("of:1");
+ private static final DeviceId DID2 = deviceId("of:2");
+ private static final PortNumber PID1 = portNumber(1);
+ private static final PortNumber PID2 = portNumber(2);
+ private static final String KEY = "distance";
+ private static final double VALUE = 100;
+
+ private AnnotationConstraint sut;
+ private Link link;
+ private LinkResourceService linkResourceService;
+
+ @Before
+ public void setUp() {
+ linkResourceService = createMock(LinkResourceService.class);
+
+ DefaultAnnotations annotations = DefaultAnnotations.builder().set(KEY, String.valueOf(VALUE)).build();
+
+ link = new DefaultLink(PID, cp(DID1, PID1), cp(DID2, PID2), DIRECT, annotations);
+ }
+
+ /**
+ * Tests the specified annotated value is less than the threshold.
+ */
+ @Test
+ public void testLessThanThreshold() {
+ double value = 120;
+ sut = new AnnotationConstraint(KEY, value);
+
+ assertThat(sut.isValid(link, linkResourceService), is(true));
+ assertThat(sut.cost(link, linkResourceService), is(closeTo(VALUE, 1.0e-6)));
+ }
+
+ /**
+ * Tests the specified annotated value is more than the threshold.
+ */
+ @Test
+ public void testMoreThanThreshold() {
+ double value = 80;
+ sut = new AnnotationConstraint(KEY, value);
+
+ assertThat(sut.isValid(link, linkResourceService), is(false));
+ assertThat(sut.cost(link, linkResourceService), is(lessThan(0.0)));
+ }
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(new AnnotationConstraint(KEY, 100), new AnnotationConstraint(KEY, 100))
+ .addEqualityGroup(new AnnotationConstraint(KEY, 120))
+ .addEqualityGroup(new AnnotationConstraint("latency", 100))
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ConstraintObjectsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ConstraintObjectsTest.java
new file mode 100644
index 00000000..743fc252
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ConstraintObjectsTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.constraint;
+
+import org.junit.Test;
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.Link;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.LambdaResource;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Unit tests for Constraint objects.
+ */
+public class ConstraintObjectsTest {
+
+ // Bandwidth Constraint
+
+ private final Bandwidth bandwidth1 = Bandwidth.bps(100.0);
+ private final Bandwidth sameAsBandwidth1 = Bandwidth.bps(100.0);
+ private final Bandwidth bandwidth2 = Bandwidth.bps(200.0);
+
+ final BandwidthConstraint bandwidthConstraint1 =
+ new BandwidthConstraint(new BandwidthResource(bandwidth1));
+ final BandwidthConstraint bandwidthConstraintSameAs1 =
+ new BandwidthConstraint(new BandwidthResource(sameAsBandwidth1));
+ final BandwidthConstraint bandwidthConstraint2 =
+ new BandwidthConstraint(new BandwidthResource(bandwidth2));
+
+ /**
+ * Checks that the objects were created properly.
+ */
+ @Test
+ public void testBandwidthConstraintCreation() {
+ assertThat(bandwidthConstraint1.bandwidth().toDouble(), is(equalTo(100.0)));
+ assertThat(bandwidthConstraintSameAs1.bandwidth().toDouble(), is(equalTo(100.0)));
+ assertThat(bandwidthConstraint2.bandwidth().toDouble(), is(equalTo(200.0)));
+ }
+
+ /**
+ * Checks the correctness of the equals() method.
+ */
+ @Test
+ public void testBandwidthConstraintEquals() {
+ new EqualsTester()
+ .addEqualityGroup(bandwidthConstraint1, bandwidthConstraintSameAs1)
+ .addEqualityGroup(bandwidthConstraint2)
+ .testEquals();
+ }
+
+ // Lambda Constraint
+
+ final LambdaConstraint lambdaConstraint1 =
+ new LambdaConstraint(LambdaResource.valueOf(100));
+ final LambdaConstraint lambdaConstraintSameAs1 =
+ new LambdaConstraint(LambdaResource.valueOf(100));
+ final LambdaConstraint lambdaConstraint2 =
+ new LambdaConstraint(LambdaResource.valueOf(200));
+
+ /**
+ * Checks that the objects were created properly.
+ */
+ @Test
+ public void testLambdaConstraintCreation() {
+ assertThat(lambdaConstraint1.lambda().toInt(), is(equalTo(100)));
+ assertThat(lambdaConstraintSameAs1.lambda().toInt(), is(equalTo(100)));
+ assertThat(lambdaConstraint2.lambda().toInt(), is(equalTo(200)));
+ }
+
+ /**
+ * Checks the correctness of the equals() method.
+ */
+ @Test
+ public void testLambdaConstraintEquals() {
+ new EqualsTester()
+ .addEqualityGroup(lambdaConstraint1, lambdaConstraintSameAs1)
+ .addEqualityGroup(lambdaConstraint2)
+ .testEquals();
+ }
+
+ // LinkType Constraint
+
+ final LinkTypeConstraint linkTypeConstraint1 =
+ new LinkTypeConstraint(true, Link.Type.OPTICAL, Link.Type.TUNNEL);
+ final LinkTypeConstraint linkTypeConstraintSameAs1 =
+ new LinkTypeConstraint(true, Link.Type.OPTICAL, Link.Type.TUNNEL);
+ final LinkTypeConstraint linkTypeConstraint2 =
+ new LinkTypeConstraint(true, Link.Type.OPTICAL, Link.Type.DIRECT);
+
+ /**
+ * Checks that the objects were created properly.
+ */
+ @Test
+ public void testLinkTypeConstraintCreation() {
+ assertThat(linkTypeConstraint1.isInclusive(), is(true));
+ assertThat(linkTypeConstraint1.types(),
+ contains(Link.Type.OPTICAL, Link.Type.TUNNEL));
+ assertThat(linkTypeConstraintSameAs1.isInclusive(), is(true));
+ assertThat(linkTypeConstraintSameAs1.types(),
+ contains(Link.Type.OPTICAL, Link.Type.TUNNEL));
+ assertThat(linkTypeConstraint2.isInclusive(), is(true));
+ assertThat(linkTypeConstraint2.types(),
+ contains(Link.Type.OPTICAL, Link.Type.DIRECT));
+ }
+
+ /**
+ * Checks the correctness of the equals() method.
+ */
+ @Test
+ public void testLinkTypeConstraintEquals() {
+ new EqualsTester()
+ .addEqualityGroup(linkTypeConstraint1, linkTypeConstraintSameAs1)
+ .addEqualityGroup(linkTypeConstraint2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/LatencyConstraintTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/LatencyConstraintTest.java
new file mode 100644
index 00000000..bab17495
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/LatencyConstraintTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.constraint;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+
+import static org.easymock.EasyMock.createMock;
+import static org.hamcrest.Matchers.closeTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.net.AnnotationKeys.LATENCY;
+import static org.onosproject.net.DefaultLinkTest.cp;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+
+public class LatencyConstraintTest {
+
+ private static final DeviceId DID1 = deviceId("of:1");
+ private static final DeviceId DID2 = deviceId("of:2");
+ private static final DeviceId DID3 = deviceId("of:3");
+ private static final PortNumber PN1 = PortNumber.portNumber(1);
+ private static final PortNumber PN2 = PortNumber.portNumber(2);
+ private static final PortNumber PN3 = PortNumber.portNumber(3);
+ private static final PortNumber PN4 = PortNumber.portNumber(4);
+ private static final ProviderId PROVIDER_ID = new ProviderId("of", "foo");
+ private static final String LATENCY1 = "3.0";
+ private static final String LATENCY2 = "4.0";
+
+ private LatencyConstraint sut;
+ private LinkResourceService linkResourceService;
+
+ private Path path;
+ private Link link1;
+ private Link link2;
+
+ @Before
+ public void setUp() {
+ linkResourceService = createMock(LinkResourceService.class);
+
+ Annotations annotations1 = DefaultAnnotations.builder().set(LATENCY, LATENCY1).build();
+ Annotations annotations2 = DefaultAnnotations.builder().set(LATENCY, LATENCY2).build();
+
+ link1 = new DefaultLink(PROVIDER_ID, cp(DID1, PN1), cp(DID2, PN2), DIRECT, annotations1);
+ link2 = new DefaultLink(PROVIDER_ID, cp(DID2, PN3), cp(DID3, PN4), DIRECT, annotations2);
+ path = new DefaultPath(PROVIDER_ID, Arrays.asList(link1, link2), 10);
+ }
+
+ /**
+ * Tests the path latency is less than the supplied constraint.
+ */
+ @Test
+ public void testLessThanLatency() {
+ sut = new LatencyConstraint(Duration.of(10, ChronoUnit.MICROS));
+
+ assertThat(sut.validate(path, linkResourceService), is(true));
+ }
+
+ /**
+ * Tests the path latency is more than the supplied constraint.
+ */
+ @Test
+ public void testMoreThanLatency() {
+ sut = new LatencyConstraint(Duration.of(3, ChronoUnit.MICROS));
+
+ assertThat(sut.validate(path, linkResourceService), is(false));
+ }
+
+ /**
+ * Tests the link latency is equal to "latency" annotated value.
+ */
+ @Test
+ public void testCost() {
+ sut = new LatencyConstraint(Duration.of(10, ChronoUnit.MICROS));
+
+ assertThat(sut.cost(link1, linkResourceService), is(closeTo(Double.parseDouble(LATENCY1), 1.0e-6)));
+ assertThat(sut.cost(link2, linkResourceService), is(closeTo(Double.parseDouble(LATENCY2), 1.0e-6)));
+ }
+
+ /**
+ * Tests equality of the instances.
+ */
+ @Test
+ public void testEquality() {
+ LatencyConstraint c1 = new LatencyConstraint(Duration.of(1, ChronoUnit.SECONDS));
+ LatencyConstraint c2 = new LatencyConstraint(Duration.of(1000, ChronoUnit.MILLIS));
+
+ LatencyConstraint c3 = new LatencyConstraint(Duration.of(2, ChronoUnit.SECONDS));
+ LatencyConstraint c4 = new LatencyConstraint(Duration.of(2000, ChronoUnit.MILLIS));
+
+ new EqualsTester()
+ .addEqualityGroup(c1, c2)
+ .addEqualityGroup(c3, c4)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ObstacleConstraintTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ObstacleConstraintTest.java
new file mode 100644
index 00000000..f02787f3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/ObstacleConstraintTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.constraint;
+
+/**
+ * Test for constraint of intermediate nodes not passed.
+ */
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Arrays;
+
+import static org.easymock.EasyMock.createMock;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+import static org.onosproject.net.DefaultLinkTest.cp;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+
+public class ObstacleConstraintTest {
+
+ private static final DeviceId DID1 = deviceId("of:1");
+ private static final DeviceId DID2 = deviceId("of:2");
+ private static final DeviceId DID3 = deviceId("of:3");
+ private static final DeviceId DID4 = deviceId("of:4");
+ private static final PortNumber PN1 = PortNumber.portNumber(1);
+ private static final PortNumber PN2 = PortNumber.portNumber(2);
+ private static final PortNumber PN3 = PortNumber.portNumber(3);
+ private static final PortNumber PN4 = PortNumber.portNumber(4);
+ private static final ProviderId PROVIDER_ID = new ProviderId("of", "foo");
+
+ private LinkResourceService linkResourceService;
+
+ private Path path;
+ private DefaultLink link2;
+ private DefaultLink link1;
+
+ private ObstacleConstraint sut;
+
+ @Before
+ public void setUp() {
+ linkResourceService = createMock(LinkResourceService.class);
+
+ link1 = new DefaultLink(PROVIDER_ID, cp(DID1, PN1), cp(DID2, PN2), DIRECT);
+ link2 = new DefaultLink(PROVIDER_ID, cp(DID2, PN3), cp(DID3, PN4), DIRECT);
+ path = new DefaultPath(PROVIDER_ID, Arrays.asList(link1, link2), 10);
+ }
+
+ @Test
+ public void testEquality() {
+ ObstacleConstraint o1 = new ObstacleConstraint(DID1, DID2, DID3);
+ ObstacleConstraint o2 = new ObstacleConstraint(DID3, DID2, DID1);
+ ObstacleConstraint o3 = new ObstacleConstraint(DID1, DID2);
+ ObstacleConstraint o4 = new ObstacleConstraint(DID2, DID1);
+
+ new EqualsTester()
+ .addEqualityGroup(o1, o2)
+ .addEqualityGroup(o3, o4)
+ .testEquals();
+ }
+
+ /**
+ * Tests the specified path avoids the specified obstacle.
+ */
+ @Test
+ public void testPathNotThroughObstacles() {
+ sut = new ObstacleConstraint(DID4);
+
+ assertThat(sut.validate(path, linkResourceService), is(true));
+ }
+
+ /**
+ * Test the specified path does not avoid the specified obstacle.
+ */
+ @Test
+ public void testPathThroughObstacle() {
+ sut = new ObstacleConstraint(DID1);
+
+ assertThat(sut.validate(path, linkResourceService), is(false));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/WaypointConstraintTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/WaypointConstraintTest.java
new file mode 100644
index 00000000..f7e212a3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/intent/constraint/WaypointConstraintTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.constraint;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.LinkResourceService;
+
+import java.util.Arrays;
+
+import static org.easymock.EasyMock.createMock;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.net.DefaultLinkTest.cp;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+
+/**
+ * Test for constraint of intermediate elements.
+ */
+public class WaypointConstraintTest {
+
+ private static final DeviceId DID1 = deviceId("of:1");
+ private static final DeviceId DID2 = deviceId("of:2");
+ private static final DeviceId DID3 = deviceId("of:3");
+ private static final DeviceId DID4 = deviceId("of:4");
+ private static final PortNumber PN1 = PortNumber.portNumber(1);
+ private static final PortNumber PN2 = PortNumber.portNumber(2);
+ private static final PortNumber PN3 = PortNumber.portNumber(3);
+ private static final PortNumber PN4 = PortNumber.portNumber(4);
+ private static final ProviderId PROVIDER_ID = new ProviderId("of", "foo");
+
+ private WaypointConstraint sut;
+ private LinkResourceService linkResourceService;
+
+ private Path path;
+ private DefaultLink link2;
+ private DefaultLink link1;
+
+ @Before
+ public void setUp() {
+ linkResourceService = createMock(LinkResourceService.class);
+
+ link1 = new DefaultLink(PROVIDER_ID, cp(DID1, PN1), cp(DID2, PN2), DIRECT);
+ link2 = new DefaultLink(PROVIDER_ID, cp(DID2, PN3), cp(DID3, PN4), DIRECT);
+ path = new DefaultPath(PROVIDER_ID, Arrays.asList(link1, link2), 10);
+ }
+
+ /**
+ * Tests that all of the specified waypoints are included in the specified path in order.
+ */
+ @Test
+ public void testSatisfyWaypoints() {
+ sut = new WaypointConstraint(DID1, DID2, DID3);
+
+ assertThat(sut.validate(path, linkResourceService), is(true));
+ }
+
+ /**
+ * Tests that the specified path does not includes the specified waypoint.
+ */
+ @Test
+ public void testNotSatisfyWaypoint() {
+ sut = new WaypointConstraint(DID4);
+
+ assertThat(sut.validate(path, linkResourceService), is(false));
+ }
+
+ @Test
+ public void testEquality() {
+ Constraint c1 = new WaypointConstraint(DID1, DID2);
+ Constraint c2 = new WaypointConstraint(DID1, DID2);
+
+ Constraint c3 = new WaypointConstraint(DID2);
+ Constraint c4 = new WaypointConstraint(DID3);
+
+ new EqualsTester()
+ .addEqualityGroup(c1, c2)
+ .addEqualityGroup(c3)
+ .addEqualityGroup(c4)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/DefaultLinkDescriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/DefaultLinkDescriptionTest.java
new file mode 100644
index 00000000..3b98c5ba
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/DefaultLinkDescriptionTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DefaultLinkTest.cp;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the default link description.
+ */
+public class DefaultLinkDescriptionTest {
+
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+ private static final PortNumber P1 = portNumber(1);
+
+ @Test
+ public void basics() {
+ LinkDescription desc = new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P1), DIRECT);
+ assertEquals("incorrect src", cp(DID1, P1), desc.src());
+ assertEquals("incorrect dst", cp(DID2, P1), desc.dst());
+ assertEquals("incorrect type", DIRECT, desc.type());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkEventTest.java
new file mode 100644
index 00000000..307fa69a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkEventTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.onosproject.event.AbstractEventTest;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Tests of the device event.
+ */
+public class LinkEventTest extends AbstractEventTest {
+
+ private Link createLink() {
+ return new DefaultLink(new ProviderId("of", "foo"),
+ new ConnectPoint(deviceId("of:foo"), portNumber(1)),
+ new ConnectPoint(deviceId("of:bar"), portNumber(2)),
+ Link.Type.INDIRECT);
+ }
+
+ @Test
+ public void withTime() {
+ Link link = createLink();
+ LinkEvent event = new LinkEvent(LinkEvent.Type.LINK_ADDED, link, 123L);
+ validateEvent(event, LinkEvent.Type.LINK_ADDED, link, 123L);
+ }
+
+ @Test
+ public void withoutTime() {
+ Link link = createLink();
+ long before = System.currentTimeMillis();
+ LinkEvent event = new LinkEvent(LinkEvent.Type.LINK_ADDED, link);
+ long after = System.currentTimeMillis();
+ validateEvent(event, LinkEvent.Type.LINK_ADDED, link, before, after);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkServiceAdapter.java
new file mode 100644
index 00000000..5cb84b31
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/link/LinkServiceAdapter.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.link;
+
+import java.util.Set;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.State;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+
+/**
+ * Test adapter for link service.
+ */
+public class LinkServiceAdapter implements LinkService {
+ @Override
+ public int getLinkCount() {
+ return 0;
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return null;
+ }
+
+ @Override
+ public Iterable<Link> getActiveLinks() {
+ 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) {
+ return null;
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public Set<Link> getLinks(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Set<Link> getEgressLinks(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Set<Link> getIngressLinks(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Link getLink(ConnectPoint src, ConnectPoint dst) {
+ return null;
+ }
+
+ @Override
+ public void addListener(LinkListener listener) {
+ }
+
+ @Override
+ public void removeListener(LinkListener listener) {
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/DefaultMeterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/DefaultMeterTest.java
new file mode 100644
index 00000000..b39d2efb
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/DefaultMeterTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.meter;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.did;
+
+
+/**
+ * DefaultMeter Tests.
+ */
+public class DefaultMeterTest {
+
+ private Meter m1;
+ private Meter sameAsm1;
+ private Meter m2;
+
+ @Before
+ public void setup() {
+
+ Band band = DefaultBand.builder()
+ .ofType(Band.Type.DROP)
+ .withRate(500)
+ .build();
+
+ m1 = DefaultMeter.builder()
+ .forDevice(did("1"))
+ .fromApp(APP_ID)
+ .withId(MeterId.meterId(1))
+ .withUnit(Meter.Unit.KB_PER_SEC)
+ .withBands(Collections.singletonList(band))
+ .build();
+
+ sameAsm1 = DefaultMeter.builder()
+ .forDevice(did("1"))
+ .fromApp(APP_ID)
+ .withId(MeterId.meterId(1))
+ .withUnit(Meter.Unit.KB_PER_SEC)
+ .withBands(Collections.singletonList(band))
+ .build();
+
+ m2 = DefaultMeter.builder()
+ .forDevice(did("2"))
+ .fromApp(APP_ID)
+ .withId(MeterId.meterId(2))
+ .withUnit(Meter.Unit.KB_PER_SEC)
+ .withBands(Collections.singletonList(band))
+ .build();
+
+ }
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(m1, sameAsm1)
+ .addEqualityGroup(m2).testEquals();
+ }
+
+ @Test
+ public void testConstruction() {
+ DefaultMeter m = (DefaultMeter) m1;
+
+ assertThat(m.deviceId(), is(did("1")));
+ assertThat(m.appId(), is(APP_ID));
+ assertThat(m.id(), is(MeterId.meterId(1)));
+ assertThat(m.isBurst(), is(false));
+
+ assertThat(m.life(), is(0L));
+ assertThat(m.bytesSeen(), is(0L));
+ assertThat(m.packetsSeen(), is(0L));
+ assertThat(m.referenceCount(), is(0L));
+
+ }
+
+
+
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.java
new file mode 100644
index 00000000..ad53e3a3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/meter/MeterOperationTest.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.meter;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Collection;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+
+/**
+ * Unit tests for the MeterOperationTest object.
+ */
+public class MeterOperationTest {
+
+
+
+ /**
+ * Checks that the MeterOperation class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutableBaseClass(MeterOperation.class);
+ }
+
+ @Test
+ public void testEquality() {
+ final Meter m1 = new TestMeter();
+ final Meter m2 = new TestMeter();
+ final MeterOperation op1 = new MeterOperation(m1,
+ MeterOperation.Type.ADD);
+ final MeterOperation sameAsOp1 = new MeterOperation(m1,
+ MeterOperation.Type.ADD);
+ final MeterOperation op2 = new MeterOperation(m2,
+ MeterOperation.Type.ADD);
+
+ new EqualsTester()
+ .addEqualityGroup(op1, sameAsOp1)
+ .addEqualityGroup(op2)
+ .testEquals();
+ }
+
+ @Test
+ public void testConstruction() {
+ final Meter m1 = new TestMeter();
+
+ final MeterOperation op = new MeterOperation(m1, MeterOperation.Type.ADD);
+
+ assertThat(op.meter(), is(m1));
+ }
+
+ private static final class TestMeter implements Meter {
+
+ @Override
+ public DeviceId deviceId() {
+ return null;
+ }
+
+ @Override
+ public MeterId id() {
+ return null;
+ }
+
+ @Override
+ public ApplicationId appId() {
+ return null;
+ }
+
+ @Override
+ public Unit unit() {
+ return null;
+ }
+
+ @Override
+ public boolean isBurst() {
+ return false;
+ }
+
+ @Override
+ public Collection<Band> bands() {
+ return null;
+ }
+
+ @Override
+ public MeterState state() {
+ return null;
+ }
+
+ @Override
+ public long life() {
+ return 0;
+ }
+
+ @Override
+ public long referenceCount() {
+ return 0;
+ }
+
+ @Override
+ public long packetsSeen() {
+ return 0;
+ }
+
+ @Override
+ public long bytesSeen() {
+ return 0;
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourceAllocationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourceAllocationTest.java
new file mode 100644
index 00000000..a84927a0
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourceAllocationTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.IntentId;
+
+public class ResourceAllocationTest {
+
+ private static final DeviceId D1 = DeviceId.deviceId("of:001");
+ private static final DeviceId D2 = DeviceId.deviceId("of:002");
+ private static final PortNumber P1 = PortNumber.portNumber(1);
+ private static final ConnectPoint CP1_1 = new ConnectPoint(D1, P1);
+ private static final ConnectPoint CP2_1 = new ConnectPoint(D2, P1);
+ private static final VlanId VLAN1 = VlanId.vlanId((short) 100);
+ private static final IntentId IID1 = IntentId.valueOf(30);
+ private static final LinkKey LK1 = LinkKey.linkKey(CP1_1, CP2_1);
+ private static final LinkKey LK2 = LinkKey.linkKey(CP2_1, CP1_1);
+
+ @Test
+ public void testEquals() {
+ ResourceAllocation alloc1 = new ResourceAllocation(new ResourcePath(LK1, VLAN1), IID1);
+ ResourceAllocation sameAsAlloc1 = new ResourceAllocation(new ResourcePath(LK1, VLAN1), IID1);
+ ResourceAllocation alloc2 = new ResourceAllocation(new ResourcePath(LK2, VLAN1), IID1);
+
+ new EqualsTester()
+ .addEqualityGroup(alloc1, sameAsAlloc1)
+ .addEqualityGroup(alloc2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java
new file mode 100644
index 00000000..4a8886a4
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.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.newresource;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ResourcePathTest {
+
+ private static final DeviceId D1 = DeviceId.deviceId("of:001");
+ private static final DeviceId D2 = DeviceId.deviceId("of:002");
+ private static final PortNumber P1 = PortNumber.portNumber(1);
+ private static final ConnectPoint CP1_1 = new ConnectPoint(D1, P1);
+ private static final ConnectPoint CP2_1 = new ConnectPoint(D2, P1);
+ private static final VlanId VLAN1 = VlanId.vlanId((short) 100);
+
+ @Test
+ public void testEquals() {
+ ResourcePath resource1 = new ResourcePath(LinkKey.linkKey(CP1_1, CP2_1), VLAN1);
+ ResourcePath sameAsResource1 = new ResourcePath(LinkKey.linkKey(CP1_1, CP2_1), VLAN1);
+ ResourcePath resource2 = new ResourcePath(LinkKey.linkKey(CP2_1, CP1_1), VLAN1);
+
+ new EqualsTester()
+ .addEqualityGroup(resource1, sameAsResource1)
+ .addEqualityGroup(resource2)
+ .testEquals();
+ }
+
+ @Test
+ public void testCreateWithZeroComponent() {
+ ResourcePath path = new ResourcePath();
+
+ assertThat(path, is(ResourcePath.ROOT));
+ }
+
+ @Test
+ public void testThereIsParent() {
+ ResourcePath path = new ResourcePath(LinkKey.linkKey(CP1_1, CP2_1), VLAN1);
+ ResourcePath parent = new ResourcePath(LinkKey.linkKey(CP1_1, CP2_1));
+
+ assertThat(path.parent(), is(Optional.of(parent)));
+ }
+
+ @Test
+ public void testNoParent() {
+ ResourcePath path = new ResourcePath(LinkKey.linkKey(CP1_1, CP2_1));
+
+ assertThat(path.parent(), is(Optional.of(ResourcePath.ROOT)));
+ }
+
+ @Test
+ public void testBase() {
+ LinkKey linkKey = LinkKey.linkKey(CP1_1, CP2_1);
+ ResourcePath path = new ResourcePath(linkKey);
+
+ LinkKey child = (LinkKey) path.lastComponent();
+ assertThat(child, is(linkKey));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultInboundPacketTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultInboundPacketTest.java
new file mode 100644
index 00000000..eda68240
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultInboundPacketTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.packet;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.connectPoint;
+
+/**
+ * Unit tests for the DefaultInboundPacket class.
+ */
+public class DefaultInboundPacketTest {
+
+ final Ethernet eth = new Ethernet()
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setSourceMACAddress(MacAddress.BROADCAST);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(eth.serialize());
+ final DefaultInboundPacket packet1 =
+ new DefaultInboundPacket(connectPoint("d1", 1),
+ eth,
+ byteBuffer);
+ final DefaultInboundPacket sameAsPacket1 =
+ new DefaultInboundPacket(connectPoint("d1", 1),
+ eth,
+ byteBuffer);
+ final DefaultInboundPacket packet2 =
+ new DefaultInboundPacket(connectPoint("d2", 1),
+ eth,
+ byteBuffer);
+ /**
+ * Checks that the DefaultInboundPacket class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(DefaultInboundPacket.class);
+ }
+
+ /**
+ * Tests the equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(packet1, sameAsPacket1)
+ .addEqualityGroup(packet2)
+ .testEquals();
+ }
+
+ /**
+ * Tests the object creation through the constructor.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(packet1.receivedFrom(), equalTo(connectPoint("d1", 1)));
+ assertThat(packet1.parsed(), equalTo(eth));
+ assertThat(packet1.unparsed(), notNullValue());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java
new file mode 100644
index 00000000..21f997d9
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.packet;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Unit tests for the DefaultOutboundPacketTest class.
+ */
+public class DefaultOutboundPacketTest {
+ final Ethernet eth = new Ethernet()
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setSourceMACAddress(MacAddress.BROADCAST);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(eth.serialize());
+ final TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
+ final DefaultOutboundPacket packet1 =
+ new DefaultOutboundPacket(did("d1"),
+ treatment,
+ byteBuffer);
+ final DefaultOutboundPacket sameAsPacket1 =
+ new DefaultOutboundPacket(did("d1"),
+ treatment,
+ byteBuffer);
+ final DefaultOutboundPacket packet2 =
+ new DefaultOutboundPacket(did("d2"),
+ treatment,
+ byteBuffer);
+ /**
+ * Checks that the DefaultOutboundPacket class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(DefaultOutboundPacket.class);
+ }
+
+ /**
+ * Tests the equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(packet1, sameAsPacket1)
+ .addEqualityGroup(packet2)
+ .testEquals();
+ }
+
+ /**
+ * Tests the object creation through the constructor.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(packet1.sendThrough(), equalTo(did("d1")));
+ assertThat(packet1.data(), equalTo(byteBuffer));
+ assertThat(packet1.treatment(), equalTo(treatment));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultPacketContextTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultPacketContextTest.java
new file mode 100644
index 00000000..ee7ae7e5
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/DefaultPacketContextTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.packet;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+import static org.onosproject.net.NetTestTools.connectPoint;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Unit tests for the DefaultPacketContextTest.
+ */
+public class DefaultPacketContextTest {
+ final Ethernet eth = new Ethernet()
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setSourceMACAddress(MacAddress.BROADCAST);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(eth.serialize());
+ final DefaultInboundPacket inPacket =
+ new DefaultInboundPacket(connectPoint("d1", 1),
+ eth,
+ byteBuffer);
+ final TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
+ final DefaultOutboundPacket outPacket =
+ new DefaultOutboundPacket(did("d1"),
+ treatment,
+ byteBuffer);
+
+ static class MockPacketContext extends DefaultPacketContext {
+
+ protected MockPacketContext(long time, InboundPacket inPkt,
+ OutboundPacket outPkt, boolean block) {
+ super(time, inPkt, outPkt, block);
+ }
+
+ @Override
+ public void send() {
+
+ }
+
+ @Override
+ public boolean block() {
+ return super.block();
+ }
+ }
+
+ final DefaultPacketContext context1 =
+ new MockPacketContext(123L, inPacket, outPacket, true);
+ final DefaultPacketContext sameAsContext1 =
+ new MockPacketContext(123L, inPacket, outPacket, true);
+ final DefaultPacketContext context2 =
+ new MockPacketContext(123123L, inPacket, outPacket, true);
+
+ /**
+ * Checks that the DefaultOutboundPacket class is immutable but can be
+ * used as a base class.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutableBaseClass(DefaultPacketContext.class);
+ }
+
+ /**
+ * Tests the equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ // No hashCode() or equals() defined, object comparison is used.
+ new EqualsTester()
+ .addEqualityGroup(context1)
+ .addEqualityGroup(sameAsContext1)
+ .addEqualityGroup(context2)
+ .testEquals();
+ }
+
+ /**
+ * Tests that objects are created properly.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(context1.block(), is(true));
+ assertThat(context1.inPacket(), is(inPacket));
+ assertThat(context1.isHandled(), is(true));
+ assertThat(context1.outPacket(), is(outPacket));
+ assertThat(context1.time(), is(123L));
+ assertThat(context1.treatmentBuilder(), is(notNullValue()));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.java
new file mode 100644
index 00000000..afe936b7
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/packet/PacketServiceAdapter.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.packet;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.TrafficSelector;
+
+/**
+ * Test adapter for packet service.
+ */
+public class PacketServiceAdapter implements PacketService {
+ @Override
+ public void addProcessor(PacketProcessor processor, int priority) {
+ }
+
+ @Override
+ public void removeProcessor(PacketProcessor processor) {
+ }
+
+ @Override
+ public void requestPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
+ }
+
+ @Override
+ public void cancelPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderRegistryTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderRegistryTest.java
new file mode 100644
index 00000000..f08d93bd
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderRegistryTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.provider;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the base provider registry.
+ */
+public class AbstractProviderRegistryTest {
+
+ private class TestProviderService extends AbstractProviderService<TestProvider> {
+ protected TestProviderService(TestProvider provider) {
+ super(provider);
+ }
+ }
+
+ private class TestProviderRegistry extends AbstractProviderRegistry<TestProvider, TestProviderService> {
+ @Override
+ protected TestProviderService createProviderService(TestProvider provider) {
+ return new TestProviderService(provider);
+ }
+ }
+
+ @Test
+ public void basics() {
+ TestProviderRegistry registry = new TestProviderRegistry();
+ assertEquals("incorrect provider count", 0, registry.getProviders().size());
+
+ ProviderId fooId = new ProviderId("of", "foo");
+ TestProvider pFoo = new TestProvider(fooId);
+ TestProviderService psFoo = registry.register(pFoo);
+ assertEquals("incorrect provider count", 1, registry.getProviders().size());
+ assertThat("provider not found", registry.getProviders().contains(fooId));
+ assertEquals("incorrect provider", psFoo.provider(), pFoo);
+
+ ProviderId barId = new ProviderId("snmp", "bar");
+ TestProvider pBar = new TestProvider(barId);
+ TestProviderService psBar = registry.register(pBar);
+ assertEquals("incorrect provider count", 2, registry.getProviders().size());
+ assertThat("provider not found", registry.getProviders().contains(barId));
+ assertEquals("incorrect provider", psBar.provider(), pBar);
+
+ psFoo.checkValidity();
+ registry.unregister(pFoo);
+ psBar.checkValidity();
+ assertEquals("incorrect provider count", 1, registry.getProviders().size());
+ assertThat("provider not found", registry.getProviders().contains(barId));
+ }
+
+ @Test
+ public void ancillaryProviders() {
+ TestProviderRegistry registry = new TestProviderRegistry();
+ TestProvider pFoo = new TestProvider(new ProviderId("of", "foo"));
+ TestProvider pBar = new TestProvider(new ProviderId("of", "bar", true));
+ registry.register(pFoo);
+ registry.register(pBar);
+ assertEquals("incorrect provider count", 2, registry.getProviders().size());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void duplicateRegistration() {
+ TestProviderRegistry registry = new TestProviderRegistry();
+ TestProvider pFoo = new TestProvider(new ProviderId("of", "foo"));
+ registry.register(pFoo);
+ registry.register(pFoo);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void duplicateSchemeRegistration() {
+ TestProviderRegistry registry = new TestProviderRegistry();
+ TestProvider pFoo = new TestProvider(new ProviderId("of", "foo"));
+ TestProvider pBar = new TestProvider(new ProviderId("of", "bar"));
+ registry.register(pFoo);
+ registry.register(pBar);
+ }
+
+ @Test
+ public void voidUnregistration() {
+ TestProviderRegistry registry = new TestProviderRegistry();
+ registry.unregister(new TestProvider(new ProviderId("of", "foo")));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unregistration() {
+ TestProviderRegistry registry = new TestProviderRegistry();
+ TestProvider pFoo = new TestProvider(new ProviderId("of", "foo"));
+ TestProviderService psFoo = registry.register(pFoo);
+ registry.unregister(pFoo);
+ psFoo.checkValidity();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderTest.java
new file mode 100644
index 00000000..e9e06c76
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/AbstractProviderTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.provider;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the base provider implementation.
+ */
+public class AbstractProviderTest {
+
+ @Test
+ public void basics() {
+ ProviderId id = new ProviderId("of", "foo.bar");
+ TestProvider provider = new TestProvider(id);
+ assertEquals("incorrect id", id, provider.id());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/ProviderIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/ProviderIdTest.java
new file mode 100644
index 00000000..7e973991
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/ProviderIdTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.provider;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+/**
+ * Test of the provider identifier.
+ */
+public class ProviderIdTest {
+
+ @Test
+ public void basics() {
+ new EqualsTester()
+ .addEqualityGroup(new ProviderId("of", "foo"), new ProviderId("of", "foo"))
+ .addEqualityGroup(new ProviderId("snmp", "foo"), new ProviderId("snmp", "foo"))
+ .addEqualityGroup(new ProviderId("of", "bar"))
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/TestProvider.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/TestProvider.java
new file mode 100644
index 00000000..2f521bbb
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/provider/TestProvider.java
@@ -0,0 +1,32 @@
+/*
+ * 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.provider;
+
+/**
+ * Test provider fixture.
+ */
+public class TestProvider extends AbstractProvider {
+
+ /**
+ * Creates a provider with the supplier identifier.
+ *
+ * @param id provider id
+ */
+ protected TestProvider(ProviderId id) {
+ super(id);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/resource/MplsObjectsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/resource/MplsObjectsTest.java
new file mode 100644
index 00000000..56f7a477
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/resource/MplsObjectsTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+import org.onosproject.net.resource.link.MplsLabel;
+import org.onosproject.net.resource.link.MplsLabelResourceAllocation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Unit tests for MPLS objects.
+ */
+public class MplsObjectsTest {
+
+ MplsLabel label1 = MplsLabel.valueOf(1);
+ MplsLabel label2 = MplsLabel.valueOf(2);
+ MplsLabel sameAsLabel1 = MplsLabel.valueOf(1);
+ MplsLabel sameAsLabel2 = MplsLabel.valueOf(2);
+ MplsLabel label3 = MplsLabel.valueOf(3);
+
+ /**
+ * Tests creation of MPLS label objects.
+ */
+ @Test
+ public void checkLabelConstruction() {
+ assertThat(label1.label().toInt(), is(1));
+ }
+
+ /**
+ * Tests the operation of equals(), hashCode() and toString().
+ */
+ @Test
+ public void testLabelEqualsOperation() {
+ new EqualsTester()
+ .addEqualityGroup(label1, sameAsLabel1)
+ .addEqualityGroup(label2, sameAsLabel2)
+ .addEqualityGroup(label3)
+ .testEquals();
+ }
+
+ MplsLabelResourceAllocation labelAllocation1 =
+ new MplsLabelResourceAllocation(label1);
+ MplsLabelResourceAllocation sameAsLabelAllocation1 =
+ new MplsLabelResourceAllocation(label1);
+ MplsLabelResourceAllocation labelAllocation2 =
+ new MplsLabelResourceAllocation(label2);
+ MplsLabelResourceAllocation sameAsLabelAllocation2 =
+ new MplsLabelResourceAllocation(label2);
+ MplsLabelResourceAllocation labelAllocation3 =
+ new MplsLabelResourceAllocation(label3);
+
+ /**
+ * Tests creation of MPLS label objects.
+ */
+ @Test
+ public void checkLabelResourceAllocationConstruction() {
+ assertThat(labelAllocation1.mplsLabel().label().toInt(), is(1));
+ assertThat(labelAllocation1.type(), is(ResourceType.MPLS_LABEL));
+ }
+
+ /**
+ * Tests the operation of equals(), hashCode() and toString().
+ */
+ @Test
+ public void testLabelResourceAllocationEqualsOperation() {
+ new EqualsTester()
+ .addEqualityGroup(labelAllocation1, sameAsLabelAllocation1)
+ .addEqualityGroup(labelAllocation2, sameAsLabelAllocation2)
+ .addEqualityGroup(labelAllocation3)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/DefaultLoadTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/DefaultLoadTest.java
new file mode 100644
index 00000000..6b3ef93d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/DefaultLoadTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.statistic;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsNot.not;
+
+/**
+ * Unit tests for DefaultLoad class.
+ */
+public class DefaultLoadTest {
+
+ @Before
+ public void reset() {
+ DefaultLoad.setPollInterval(10);
+ }
+
+ /**
+ * Tests the default constructor.
+ */
+ @Test
+ public void testDefaultConstructor() {
+ DefaultLoad load = new DefaultLoad();
+ assertThat(load.isValid(), is(false));
+ assertThat(load.latest(), is(-1L));
+ assertThat(load.rate(), is(0L));
+ assertThat(load.time(), is(not(0)));
+ }
+
+ /**
+ * Tests the current-previous constructor.
+ */
+ @Test
+ public void testCurrentPreviousConstructor() {
+ DefaultLoad load = new DefaultLoad(20, 10);
+ assertThat(load.isValid(), is(true));
+ assertThat(load.latest(), is(20L));
+ assertThat(load.rate(), is(1L));
+ assertThat(load.time(), is(not(0)));
+ }
+
+ /**
+ * Tests the current-previous-interval constructor.
+ */
+ @Test
+ public void testCurrentPreviousIntervalConstructor() {
+ DefaultLoad load = new DefaultLoad(20, 10, 1);
+ assertThat(load.isValid(), is(true));
+ assertThat(load.latest(), is(20L));
+ assertThat(load.rate(), is(10L));
+ assertThat(load.time(), is(not(0)));
+ }
+
+ /**
+ * Tests the toString operation.
+ */
+ @Test
+ public void testToString() {
+ DefaultLoad load = new DefaultLoad(20, 10);
+
+ String s = load.toString();
+ assertThat(s, containsString("Load{rate=1, latest=20}"));
+ }
+
+ /**
+ * Tests setting the poll interval.
+ */
+ @Test
+ public void testSettingPollInterval() {
+ DefaultLoad.setPollInterval(1);
+ DefaultLoad load = new DefaultLoad(40, 10);
+ assertThat(load.rate(), is(30L));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/StatisticServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/StatisticServiceAdapter.java
new file mode 100644
index 00000000..cf8f36f8
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/statistic/StatisticServiceAdapter.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.statistic;
+
+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.FlowRule;
+
+import java.util.Optional;
+
+/**
+ * Test adapter for statistics service.
+ */
+public class StatisticServiceAdapter implements StatisticService {
+ @Override
+ public Load load(Link link) {
+ return null;
+ }
+
+ @Override
+ public Load load(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Link max(Path path) {
+ return null;
+ }
+
+ @Override
+ public Link min(Path path) {
+ return null;
+ }
+
+ @Override
+ public FlowRule highestHitter(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Load load(Link link, ApplicationId appId, Optional<GroupId> groupId) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/ClusterIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/ClusterIdTest.java
new file mode 100644
index 00000000..1eb6331f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/ClusterIdTest.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.net.topology;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.topology.ClusterId.clusterId;
+
+/**
+ * Test of the cluster ID.
+ */
+public class ClusterIdTest {
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(clusterId(1), clusterId(1))
+ .addEqualityGroup(clusterId(3), clusterId(3)).testEquals();
+ }
+
+ @Test
+ public void basics() {
+ assertEquals("incorrect index", 123, clusterId(123).index());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultGraphDescriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultGraphDescriptionTest.java
new file mode 100644
index 00000000..8b0f8f05
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultGraphDescriptionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Test;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.topology.DefaultTopologyEdgeTest.*;
+
+public class DefaultGraphDescriptionTest {
+
+ static final DefaultTopologyEdge E1 = new DefaultTopologyEdge(V1, V2, L1);
+ static final DefaultTopologyEdge E2 = new DefaultTopologyEdge(V1, V2, L1);
+
+ private static final DeviceId D3 = deviceId("3");
+
+ static final Device DEV1 = new DefaultDevice(PID, D1, SWITCH, "", "", "", "", null);
+ static final Device DEV2 = new DefaultDevice(PID, D2, SWITCH, "", "", "", "", null);
+ static final Device DEV3 = new DefaultDevice(PID, D3, SWITCH, "", "", "", "", null);
+
+ @Test
+ public void basics() {
+ DefaultGraphDescription desc =
+ new DefaultGraphDescription(4321L, ImmutableSet.of(DEV1, DEV2, DEV3),
+ ImmutableSet.of(L1, L2));
+ assertEquals("incorrect time", 4321L, desc.timestamp());
+ assertEquals("incorrect vertex count", 3, desc.vertexes().size());
+ assertEquals("incorrect edge count", 2, desc.edges().size());
+ }
+
+ @Test
+ public void missingVertex() {
+ GraphDescription desc = new DefaultGraphDescription(4321L,
+ ImmutableSet.of(DEV1, DEV3),
+ ImmutableSet.of(L1, L2));
+ assertEquals("incorrect time", 4321L, desc.timestamp());
+ assertEquals("incorrect vertex count", 2, desc.vertexes().size());
+ assertEquals("incorrect edge count", 0, desc.edges().size());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyClusterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyClusterTest.java
new file mode 100644
index 00000000..06ed8fde
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyClusterTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.topology.ClusterId.clusterId;
+
+/**
+ * Test of the default topology cluster implementation.
+ */
+public class DefaultTopologyClusterTest {
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(cluster(3, 2, 1, "of:1"), cluster(3, 2, 1, "of:1"))
+ .addEqualityGroup(cluster(3, 2, 1, "of:2"), cluster(3, 2, 1, "of:2"))
+ .addEqualityGroup(cluster(0, 2, 1, "of:1"), cluster(0, 2, 1, "of:1"))
+ .addEqualityGroup(cluster(3, 3, 1, "of:1"), cluster(3, 3, 1, "of:1"))
+ .testEquals();
+ }
+
+ @Test
+ public void basics() {
+ TopologyCluster cluster = cluster(6, 5, 4, "of:111");
+ assertEquals("incorrect id", clusterId(6), cluster.id());
+ assertEquals("incorrect id", 5, cluster.deviceCount());
+ assertEquals("incorrect id", 4, cluster.linkCount());
+ assertEquals("incorrect id", deviceId("of:111"), cluster.root().deviceId());
+
+ }
+
+ private TopologyCluster cluster(int id, int dc, int lc, String root) {
+ return new DefaultTopologyCluster(clusterId(id), dc, lc,
+ new DefaultTopologyVertex(deviceId(root)));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyEdgeTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyEdgeTest.java
new file mode 100644
index 00000000..830e9b9f
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyEdgeTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Tests of the topology graph edge.
+ */
+public class DefaultTopologyEdgeTest {
+
+ static final DeviceId D1 = deviceId("1");
+ static final DeviceId D2 = deviceId("2");
+ static final PortNumber P1 = portNumber(1);
+ static final PortNumber P2 = portNumber(2);
+
+ static final ConnectPoint CP1 = new ConnectPoint(D1, P1);
+ static final ConnectPoint CP2 = new ConnectPoint(D2, P1);
+ static final ConnectPoint CP3 = new ConnectPoint(D2, P1);
+ static final ConnectPoint CP4 = new ConnectPoint(D1, P2);
+
+ static final DefaultTopologyVertex V1 = new DefaultTopologyVertex(D1);
+ static final DefaultTopologyVertex V2 = new DefaultTopologyVertex(D2);
+
+ static final ProviderId PID = new ProviderId("foo", "bar");
+
+ /** D1:P1 -> D2:P1. */
+ static final Link L1 = new DefaultLink(PID, CP1, CP2, Link.Type.INDIRECT);
+ /** D2:P1 -> D1:P2. */
+ static final Link L2 = new DefaultLink(PID, CP3, CP4, Link.Type.INDIRECT);
+
+ @Test
+ public void basics() {
+ DefaultTopologyEdge e = new DefaultTopologyEdge(V1, V2, L1);
+ assertEquals("incorrect src", V1, e.src());
+ assertEquals("incorrect dst", V2, e.dst());
+ assertEquals("incorrect link", L1, e.link());
+
+ new EqualsTester()
+ .addEqualityGroup(new DefaultTopologyEdge(V1, V2, L1),
+ new DefaultTopologyEdge(V1, V2, L1))
+ .addEqualityGroup(new DefaultTopologyEdge(V2, V1, L2),
+ new DefaultTopologyEdge(V2, V1, L2))
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyVertexTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyVertexTest.java
new file mode 100644
index 00000000..aa01d261
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/DefaultTopologyVertexTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.DeviceId.deviceId;
+
+/**
+ * Tests of the topology graph vertex.
+ */
+public class DefaultTopologyVertexTest {
+
+ private static final DeviceId D1 = deviceId("1");
+ private static final DeviceId D2 = deviceId("2");
+
+ @Test
+ public void basics() {
+ DefaultTopologyVertex v = new DefaultTopologyVertex(D1);
+ assertEquals("incorrect device id", D1, v.deviceId());
+
+ new EqualsTester()
+ .addEqualityGroup(new DefaultTopologyVertex(D1),
+ new DefaultTopologyVertex(D1))
+ .addEqualityGroup(new DefaultTopologyVertex(D2),
+ new DefaultTopologyVertex(D2)).testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java
new file mode 100644
index 00000000..07e67842
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+
+import java.util.Set;
+
+/**
+ * Test adapter for topology service.
+ */
+public class TopologyServiceAdapter implements TopologyService {
+ @Override
+ public Topology currentTopology() {
+ return null;
+ }
+
+ @Override
+ public boolean isLatest(Topology topology) {
+ return false;
+ }
+
+ @Override
+ public TopologyGraph getGraph(Topology topology) {
+ return null;
+ }
+
+ @Override
+ public Set<TopologyCluster> getClusters(Topology topology) {
+ return null;
+ }
+
+ @Override
+ public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
+ return null;
+ }
+
+ @Override
+ public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
+ return null;
+ }
+
+ @Override
+ public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
+ return null;
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+ return null;
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst, LinkWeight weight) {
+ return null;
+ }
+
+ @Override
+ public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
+ return false;
+ }
+
+ @Override
+ public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
+ return false;
+ }
+
+ @Override
+ public void addListener(TopologyListener listener) {
+ }
+
+ @Override
+ public void removeListener(TopologyListener listener) {
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterCommunicationServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterCommunicationServiceAdapter.java
new file mode 100644
index 00000000..04f890c8
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterCommunicationServiceAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * 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.store.cluster.messaging;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Testing adapter for the cluster communication service.
+ */
+public class ClusterCommunicationServiceAdapter
+ implements ClusterCommunicationService {
+
+ @Override
+ public void addSubscriber(MessageSubject subject,
+ ClusterMessageHandler subscriber,
+ ExecutorService executor) {
+ }
+
+ @Override
+ public void removeSubscriber(MessageSubject subject) {}
+
+ @Override
+ public <M> void broadcast(M message, MessageSubject subject,
+ Function<M, byte[]> encoder) {
+ }
+
+ @Override
+ public <M> void broadcastIncludeSelf(M message,
+ MessageSubject subject, Function<M, byte[]> encoder) {
+ }
+
+ @Override
+ public <M> CompletableFuture<Void> unicast(M message, MessageSubject subject,
+ Function<M, byte[]> encoder, NodeId toNodeId) {
+ return null;
+ }
+
+ @Override
+ public <M> void multicast(M message, MessageSubject subject,
+ Function<M, byte[]> encoder, Set<NodeId> nodes) {
+ }
+
+ @Override
+ public <M, R> CompletableFuture<R> sendAndReceive(M message,
+ MessageSubject subject, Function<M, byte[]> encoder,
+ Function<byte[], R> decoder, NodeId toNodeId) {
+ return null;
+ }
+
+ @Override
+ public <M, R> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder, Function<M, R> handler,
+ Function<R, byte[]> encoder, Executor executor) {
+ }
+
+ @Override
+ public <M, R> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder, Function<M, CompletableFuture<R>> handler,
+ Function<R, byte[]> encoder) {
+ }
+
+ @Override
+ public <M> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder, Consumer<M> handler,
+ Executor executor) {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterMessageTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterMessageTest.java
new file mode 100644
index 00000000..22f841b4
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/ClusterMessageTest.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.store.cluster.messaging;
+
+import org.junit.Test;
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Units tests for ClusterMessage class.
+ */
+public class ClusterMessageTest {
+ private final MessageSubject subject1 = new MessageSubject("Message 1");
+ private final MessageSubject subject2 = new MessageSubject("Message 2");
+
+ private final byte[] payload1 = {0, 1, 2, 3, 4, 5};
+ private final byte[] payload2 = {0, 1, 2, 3, 4, 5, 6};
+
+ private final NodeId nodeId = new NodeId("node");
+
+ private final ClusterMessage message1 =
+ new ClusterMessage(nodeId, subject1, payload1);
+ private final ClusterMessage sameAsMessage1 =
+ new ClusterMessage(nodeId, subject1, payload1);
+ private final ClusterMessage message2 =
+ new ClusterMessage(nodeId, subject1, payload2);
+ private final ClusterMessage message3 =
+ new ClusterMessage(nodeId, subject2, payload1);
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(message1, sameAsMessage1)
+ .addEqualityGroup(message2)
+ .addEqualityGroup(message3)
+ .testEquals();
+ }
+
+ /**
+ * Checks the construction of a FlowId object.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(message1.payload(), is(payload1));
+ assertThat(message1.sender(), is(nodeId));
+ assertThat(message1.subject(), is(subject1));
+
+ byte[] response = {2, 2, 2, 2, 2, 2, 2, 2};
+ message1.respond(response);
+ assertThat(message1.response(), is(response));
+ }
+
+ /**
+ * Tests the toBytes and fromBytes methods.
+ */
+ @Test
+ public void testByteMethods() {
+ byte[] fromBytes = message3.getBytes();
+ ClusterMessage message = ClusterMessage.fromBytes(fromBytes);
+ assertThat(message, is(message3));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/EndpointTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/EndpointTest.java
new file mode 100644
index 00000000..c88c711a
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/EndpointTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.store.cluster.messaging;
+
+
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for the Endpoint class.
+ */
+public class EndpointTest {
+ IpAddress host1 = IpAddress.valueOf("1.2.3.4");
+ IpAddress host2 = IpAddress.valueOf("1.2.3.5");
+
+ private final Endpoint endpoint1 = new Endpoint(host1, 1);
+ private final Endpoint sameAsEndpoint1 = new Endpoint(host1, 1);
+ private final Endpoint endpoint2 = new Endpoint(host2, 1);
+ private final Endpoint endpoint3 = new Endpoint(host1, 2);
+
+ /**
+ * Checks that the MessageSubject class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(Endpoint.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(endpoint1, sameAsEndpoint1)
+ .addEqualityGroup(endpoint2)
+ .addEqualityGroup(endpoint3)
+ .testEquals();
+ }
+
+ /**
+ * Checks the construction of a MessageSubject object.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(endpoint2.host(), is(host2));
+ assertThat(endpoint2.port(), is(1));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/MessageSubjectTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/MessageSubjectTest.java
new file mode 100644
index 00000000..498edd33
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/cluster/messaging/MessageSubjectTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.cluster.messaging;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for MessageSubject class.
+ */
+public class MessageSubjectTest {
+ private final MessageSubject subject1 = new MessageSubject("Message 1");
+ private final MessageSubject sameAsSubject1 = new MessageSubject("Message 1");
+ private final MessageSubject subject2 = new MessageSubject("Message 2");
+ private final MessageSubject subject3 = new MessageSubject("Message 3");
+
+ /**
+ * Checks that the MessageSubject class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(MessageSubject.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode() and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(subject1, sameAsSubject1)
+ .addEqualityGroup(subject2)
+ .addEqualityGroup(subject3)
+ .testEquals();
+ }
+
+ /**
+ * Checks the construction of a MessageSubject object.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(subject3.value(), is("Message 3"));
+ MessageSubject serializerObject = new MessageSubject();
+ assertThat(serializerObject.value(), is(""));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/AtomicValueEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/AtomicValueEventTest.java
new file mode 100644
index 00000000..39481ca0
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/AtomicValueEventTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.store.service;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.store.service.AtomicValueEvent.Type.UPDATE;
+
+/**
+ * Unit tests for the AtomicValueEvent class.
+ */
+public class AtomicValueEventTest {
+
+ AtomicValueEvent<String> event1 =
+ new AtomicValueEvent<>("map1", UPDATE, "e1");
+ AtomicValueEvent<String> event2 =
+ new AtomicValueEvent<>("map1", UPDATE, "e2");
+ AtomicValueEvent<String> sameAsEvent2 =
+ new AtomicValueEvent<>("map1", UPDATE, "e2");
+ AtomicValueEvent<String> event3 =
+ new AtomicValueEvent<>("map2", UPDATE, "e2");
+
+ /**
+ * Checks that the SetEvent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(AtomicValueEvent.class);
+ }
+
+ /**
+ * Checks the equals(), hashCode() and toString() operations.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(event1)
+ .addEqualityGroup(event2, sameAsEvent2)
+ .addEqualityGroup(event3)
+ .testEquals();
+ }
+
+ /**
+ * Checks that construction of the object is correct.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(event1.type(), is(UPDATE));
+ assertThat(event1.value(), is("e1"));
+ assertThat(event1.name(), is("map1"));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/ConsistentMapAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/ConsistentMapAdapter.java
new file mode 100644
index 00000000..d0c1adf6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/ConsistentMapAdapter.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.store.service;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Testing adapter for the consistent map.
+ */
+public class ConsistentMapAdapter<K, V> implements ConsistentMap<K, V> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(V value) {
+ return false;
+ }
+
+ @Override
+ public Versioned<V> get(K key) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> computeIf(K key, Predicate<? super V> condition,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> put(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> putAndGet(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> remove(K key) {
+ return null;
+ }
+
+ @Override
+ public void clear() {
+
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return null;
+ }
+
+ @Override
+ public Collection<Versioned<V>> values() {
+ return null;
+ }
+
+ @Override
+ public Set<Map.Entry<K, Versioned<V>>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> putIfAbsent(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public boolean remove(K key, V value) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(K key, long version) {
+ return false;
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return false;
+ }
+
+ @Override
+ public boolean replace(K key, long oldVersion, V newValue) {
+ return false;
+ }
+
+ @Override
+ public void addListener(MapEventListener<K, V> listener) {
+
+ }
+
+ @Override
+ public void removeListener(MapEventListener<K, V> listener) {
+
+ }
+
+ @Override
+ public Map<K, V> asJavaMap() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/DatabaseUpdateTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/DatabaseUpdateTest.java
new file mode 100644
index 00000000..b498c1c7
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/DatabaseUpdateTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.store.service;
+
+import com.google.common.testing.EqualsTester;
+import junit.framework.TestCase;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Unit Tests for DatabseUpdate class.
+ */
+
+public class DatabaseUpdateTest extends TestCase {
+
+ private final DatabaseUpdate stats1 = DatabaseUpdate.newBuilder()
+ .withCurrentValue("1".getBytes())
+ .withValue("2".getBytes())
+ .withCurrentVersion(3)
+ .withKey("4")
+ .withMapName("5")
+ .withType(DatabaseUpdate.Type.PUT)
+ .build();
+
+ private final DatabaseUpdate stats2 = DatabaseUpdate.newBuilder()
+ .withCurrentValue("1".getBytes())
+ .withValue("2".getBytes())
+ .withCurrentVersion(3)
+ .withKey("4")
+ .withMapName("5")
+ .withType(DatabaseUpdate.Type.REMOVE)
+ .build();
+
+ private final DatabaseUpdate stats3 = DatabaseUpdate.newBuilder()
+ .withCurrentValue("1".getBytes())
+ .withValue("2".getBytes())
+ .withCurrentVersion(3)
+ .withKey("4")
+ .withMapName("5")
+ .withType(DatabaseUpdate.Type.REMOVE_IF_VALUE_MATCH)
+ .build();
+
+ private final DatabaseUpdate stats4 = DatabaseUpdate.newBuilder()
+ .withCurrentValue("1".getBytes())
+ .withValue("2".getBytes())
+ .withCurrentVersion(3)
+ .withKey("4")
+ .withMapName("5")
+ .withType(DatabaseUpdate.Type.REMOVE_IF_VERSION_MATCH)
+ .build();
+
+ private final DatabaseUpdate stats5 = DatabaseUpdate.newBuilder()
+ .withCurrentValue("1".getBytes())
+ .withValue("2".getBytes())
+ .withCurrentVersion(3)
+ .withKey("4")
+ .withMapName("5")
+ .withType(DatabaseUpdate.Type.PUT_IF_VALUE_MATCH)
+ .build();
+
+ private final DatabaseUpdate stats6 = DatabaseUpdate.newBuilder()
+ .withCurrentValue("1".getBytes())
+ .withValue("2".getBytes())
+ .withCurrentVersion(3)
+ .withKey("4")
+ .withMapName("5")
+ .withType(DatabaseUpdate.Type.PUT_IF_VERSION_MATCH)
+ .build();
+
+ /**
+ * Tests the constructor for the class.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(stats1.currentValue(), is("1".getBytes()));
+ assertThat(stats1.value(), is("2".getBytes()));
+ assertThat(stats1.currentVersion(), is(3L));
+ assertThat(stats1.key(), is("4"));
+ assertThat(stats1.mapName(), is("5"));
+ assertThat(stats1.type(), is(DatabaseUpdate.Type.PUT));
+ }
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(stats1, stats1)
+ .addEqualityGroup(stats2)
+ .testEquals();
+
+ new EqualsTester()
+ .addEqualityGroup(stats3, stats3)
+ .addEqualityGroup(stats4)
+ .testEquals();
+
+ new EqualsTester()
+ .addEqualityGroup(stats5, stats5)
+ .addEqualityGroup(stats6)
+ .testEquals();
+ }
+
+ /**
+ * Tests if the toString method returns a consistent value for hashing.
+ */
+ @Test
+ public void testToString() {
+ assertThat(stats1.toString(), is(stats1.toString()));
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapAdapter.java
new file mode 100644
index 00000000..07f5fb4d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.store.service;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * Testing adapter for EventuallyConsistentMap.
+ */
+public class EventuallyConsistentMapAdapter<K, V> implements EventuallyConsistentMap<K, V> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(V value) {
+ return false;
+ }
+
+ @Override
+ public V get(K key) {
+ return null;
+ }
+
+ @Override
+ public void put(K key, V value) {
+
+ }
+
+ @Override
+ public V remove(K key) {
+ return null;
+ }
+
+ @Override
+ public void remove(K key, V value) {
+
+ }
+
+ @Override
+ public V compute(K key, BiFunction<K, V, V> recomputeFunction) {
+ return null;
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+
+ }
+
+ @Override
+ public void clear() {
+
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return null;
+ }
+
+ @Override
+ public Collection<V> values() {
+ return null;
+ }
+
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public void addListener(EventuallyConsistentMapListener<K, V> listener) {
+
+ }
+
+ @Override
+ public void removeListener(EventuallyConsistentMapListener<K, V> listener) {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapEventTest.java
new file mode 100644
index 00000000..a4163cb6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/EventuallyConsistentMapEventTest.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.store.service;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+
+/**
+ * Unit tests for the EventuallyConsistentMapEvent class.
+ */
+public class EventuallyConsistentMapEventTest {
+
+ EventuallyConsistentMapEvent<String, String> event1 =
+ new EventuallyConsistentMapEvent<>("map1", PUT, "k1", "v1");
+ EventuallyConsistentMapEvent<String, String> event2 =
+ new EventuallyConsistentMapEvent<>("map1", REMOVE, "k1", "v1");
+ EventuallyConsistentMapEvent<String, String> sameAsEvent2 =
+ new EventuallyConsistentMapEvent<>("map1", REMOVE, "k1", "v1");
+ EventuallyConsistentMapEvent<String, String> event3 =
+ new EventuallyConsistentMapEvent<>("map1", PUT, "k2", "v1");
+ EventuallyConsistentMapEvent<String, String> event4 =
+ new EventuallyConsistentMapEvent<>("map1", PUT, "k1", "v2");
+ EventuallyConsistentMapEvent<String, String> event5 =
+ new EventuallyConsistentMapEvent<>("map2", REMOVE, "k1", "v2");
+ EventuallyConsistentMapEvent<String, String> event6 =
+ new EventuallyConsistentMapEvent<>("map3", REMOVE, "k1", "v2");
+
+
+ /**
+ * Checks the equals(), hashCode() and toString() operations.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(event1)
+ .addEqualityGroup(event2, sameAsEvent2)
+ .addEqualityGroup(event3)
+ .addEqualityGroup(event4)
+ .addEqualityGroup(event5)
+ .addEqualityGroup(event6)
+ .testEquals();
+ }
+
+ /**
+ * Checks that the EventuallyConsistentMapEvent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(EventuallyConsistentMapEvent.class);
+ }
+
+ /**
+ * Checks that construction of the object is correct.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(event1.type(), is(PUT));
+ assertThat(event1.key(), is("k1"));
+ assertThat(event1.value(), is("v1"));
+ assertThat(event1.name(), is("map1"));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MapEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MapEventTest.java
new file mode 100644
index 00000000..c241c0e0
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MapEventTest.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.store.service;
+
+import com.google.common.testing.EqualsTester;
+import junit.framework.TestCase;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * MapEvent unit tests.
+ */
+public class MapEventTest extends TestCase {
+
+ private final Versioned<Integer> vStats = new Versioned<>(2, 1);
+
+ private final MapEvent<String, Integer> stats1 = new MapEvent<>("a", MapEvent.Type.INSERT, "1", vStats);
+
+ private final MapEvent<String, Integer> stats2 = new MapEvent<>("a", MapEvent.Type.REMOVE, "1", vStats);
+
+ private final MapEvent<String, Integer> stats3 = new MapEvent<>("a", MapEvent.Type.UPDATE, "1", vStats);
+
+ /**
+ * Tests the creation of the MapEvent object.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(stats1.name(), is("a"));
+ assertThat(stats1.type(), is(MapEvent.Type.INSERT));
+ assertThat(stats1.key(), is("1"));
+ assertThat(stats1.value(), is(vStats));
+ }
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(stats1, stats1)
+ .addEqualityGroup(stats2)
+ .addEqualityGroup(stats3)
+ .testEquals();
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MultiValuedTimestampTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MultiValuedTimestampTest.java
new file mode 100644
index 00000000..5d991a26
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/MultiValuedTimestampTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.store.service;
+
+import com.google.common.testing.EqualsTester;
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * MultiValuedTimestamp unit tests.
+ */
+public class MultiValuedTimestampTest extends TestCase {
+
+ private final MultiValuedTimestamp<Integer, Integer> stats1 = new MultiValuedTimestamp<>(1, 3);
+
+ private final MultiValuedTimestamp<Integer, Integer> stats2 = new MultiValuedTimestamp<>(1, 2);
+
+
+ /**
+ * Tests the creation of the MapEvent object.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(stats1.value1(), is(1));
+ assertThat(stats1.value2(), is(3));
+ }
+
+ /**
+ * Tests the toCompare function.
+ */
+ @Test
+ public void testToCompare() {
+ assertThat(stats1.compareTo(stats2), is(1));
+ }
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(stats1, stats1)
+ .addEqualityGroup(stats2)
+ .testEquals();
+ }
+
+ /**
+ * Tests that the empty argument list constructor for serialization
+ * is present and creates a proper object.
+ */
+ @Test
+ public void testSerializerConstructor() {
+ try {
+ Constructor[] constructors = MultiValuedTimestamp.class.getDeclaredConstructors();
+ assertThat(constructors, notNullValue());
+ Arrays.stream(constructors).filter(ctor ->
+ ctor.getParameterTypes().length == 0)
+ .forEach(noParamsCtor -> {
+ try {
+ noParamsCtor.setAccessible(true);
+ MultiValuedTimestamp stats =
+ (MultiValuedTimestamp) noParamsCtor.newInstance();
+ assertThat(stats, notNullValue());
+ } catch (Exception e) {
+ Assert.fail("Exception instantiating no parameters constructor");
+ }
+ });
+ } catch (Exception e) {
+ Assert.fail("Exception looking up constructors");
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/SetEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/SetEventTest.java
new file mode 100644
index 00000000..5cc89d85
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/SetEventTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.store.service;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.store.service.SetEvent.Type.ADD;
+import static org.onosproject.store.service.SetEvent.Type.REMOVE;
+
+/**
+ * Unit tests for the SetEvent class.
+ */
+public class SetEventTest {
+
+ SetEvent<String> event1 =
+ new SetEvent<>("map1", ADD, "e1");
+ SetEvent<String> event2 =
+ new SetEvent<>("map1", REMOVE, "e1");
+ SetEvent<String> sameAsEvent2 =
+ new SetEvent<>("map1", REMOVE, "e1");
+ SetEvent<String> event3 =
+ new SetEvent<>("map1", ADD, "e2");
+ SetEvent<String> event4 =
+ new SetEvent<>("map1", REMOVE, "e2");
+
+ /**
+ * Checks that the SetEvent class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(EventuallyConsistentMapEvent.class);
+ }
+
+ /**
+ * Checks the equals(), hashCode() and toString() operations.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(event1)
+ .addEqualityGroup(event2, sameAsEvent2)
+ .addEqualityGroup(event3)
+ .addEqualityGroup(event4)
+ .testEquals();
+ }
+
+ /**
+ * Checks that construction of the object is correct.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(event1.type(), is(ADD));
+ assertThat(event1.entry(), is("e1"));
+ assertThat(event1.name(), is("map1"));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/StorageServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/StorageServiceAdapter.java
new file mode 100644
index 00000000..ec04e331
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/StorageServiceAdapter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.store.service;
+
+/**
+ * Adapter for the storage service.
+ */
+public class StorageServiceAdapter implements StorageService {
+ @Override
+ public <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder() {
+ return null;
+ }
+
+ @Override
+ public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
+ return null;
+ }
+
+ @Override
+ public <E> DistributedSetBuilder<E> setBuilder() {
+ return null;
+ }
+
+ @Override
+ public <E> DistributedQueueBuilder<E> queueBuilder() {
+ return null;
+ }
+
+ @Override
+ public AtomicCounterBuilder atomicCounterBuilder() {
+ return null;
+ }
+
+ @Override
+ public <V> AtomicValueBuilder<V> atomicValueBuilder() {
+ return null;
+ }
+
+ @Override
+ public TransactionContextBuilder transactionContextBuilder() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestAtomicCounter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestAtomicCounter.java
new file mode 100644
index 00000000..01209be2
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestAtomicCounter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.store.service;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Test implementation of atomic counter.
+ */
+public final class TestAtomicCounter implements AtomicCounter {
+ final AtomicLong value;
+
+ private TestAtomicCounter() {
+ value = new AtomicLong();
+ }
+
+ @Override
+ public long incrementAndGet() {
+ return value.incrementAndGet();
+ }
+
+ @Override
+ public long getAndIncrement() {
+ return value.getAndIncrement();
+ }
+
+ @Override
+ public long getAndAdd(long delta) {
+ return value.getAndAdd(delta);
+ }
+
+ @Override
+ public long addAndGet(long delta) {
+ return value.addAndGet(delta);
+ }
+
+ @Override
+ public long get() {
+ return value.get();
+ }
+
+ public static AtomicCounterBuilder builder() {
+ return new Builder();
+ }
+
+ public static class Builder implements AtomicCounterBuilder {
+ @Override
+ public AtomicCounterBuilder withName(String name) {
+ return this;
+ }
+
+ @Override
+ public AtomicCounterBuilder withPartitionsDisabled() {
+ return this;
+ }
+
+ @Override
+ public AtomicCounterBuilder withMeteringDisabled() {
+ return this;
+ }
+
+ @Override
+ public AsyncAtomicCounter buildAsyncCounter() {
+ throw new UnsupportedOperationException("Async Counter is not supported");
+ }
+
+ @Override
+ public AtomicCounter build() {
+ return new TestAtomicCounter();
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestConsistentMap.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestConsistentMap.java
new file mode 100644
index 00000000..0136a94c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestConsistentMap.java
@@ -0,0 +1,287 @@
+/*
+ * 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.store.service;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.onosproject.core.ApplicationId;
+import static org.onosproject.store.service.MapEvent.Type;
+import static org.onosproject.store.service.MapEvent.Type.*;
+
+/**
+ * Test implementation of the consistent map.
+ */
+public final class TestConsistentMap<K, V> extends ConsistentMapAdapter<K, V> {
+
+ private final List<MapEventListener<K, V>> listeners;
+ private final HashMap<K, V> map;
+ private final String mapName;
+ private final AtomicLong counter = new AtomicLong(0);
+
+ private TestConsistentMap(String mapName) {
+ map = new HashMap<>();
+ listeners = new LinkedList<>();
+ this.mapName = mapName;
+ }
+
+ private Versioned<V> version(V v) {
+ return new Versioned<>(v, counter.incrementAndGet(), System.currentTimeMillis());
+ }
+
+ /**
+ * Notify all listeners of an event.
+ */
+ private void notifyListeners(String mapName, Type type,
+ K key, Versioned<V> value) {
+ MapEvent<K, V> event = new MapEvent<>(mapName, type, key, value);
+ listeners.forEach(
+ listener -> listener.event(event)
+ );
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(V value) {
+ return map.containsValue(value);
+ }
+
+ @Override
+ public Versioned<V> get(K key) {
+ V value = map.get(key);
+ if (value != null) {
+ return version(value);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Versioned<V> computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ Versioned<V> result = version(map.computeIfAbsent(key, mappingFunction));
+ notifyListeners(mapName, INSERT, key, result);
+ return result;
+ }
+
+ @Override
+ public Versioned<V> compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return version(map.compute(key, remappingFunction));
+ }
+
+ @Override
+ public Versioned<V> computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return version(map.computeIfPresent(key, remappingFunction));
+ }
+
+ @Override
+ public Versioned<V> computeIf(K key, Predicate<? super V> condition,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return null;
+ }
+
+ @Override
+ public Versioned<V> put(K key, V value) {
+ Versioned<V> result = version(value);
+ if (map.put(key, value) == null) {
+ notifyListeners(mapName, INSERT, key, result);
+ } else {
+ notifyListeners(mapName, UPDATE, key, result);
+ }
+ return result;
+ }
+
+ @Override
+ public Versioned<V> putAndGet(K key, V value) {
+ Versioned<V> result = version(map.put(key, value));
+ notifyListeners(mapName, UPDATE, key, result);
+ return result;
+ }
+
+ @Override
+ public Versioned<V> remove(K key) {
+ Versioned<V> result = version(map.remove(key));
+ notifyListeners(mapName, REMOVE, key, result);
+ return result;
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<Versioned<V>> values() {
+ return map
+ .values()
+ .stream()
+ .map(this::version)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Set<Map.Entry<K, Versioned<V>>> entrySet() {
+ return super.entrySet();
+ }
+
+ @Override
+ public Versioned<V> putIfAbsent(K key, V value) {
+ Versioned<V> result = version(map.putIfAbsent(key, value));
+ if (map.get(key).equals(value)) {
+ notifyListeners(mapName, INSERT, key, result);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean remove(K key, V value) {
+ boolean removed = map.remove(key, value);
+ if (removed) {
+ notifyListeners(mapName, REMOVE, key, null);
+ }
+ return removed;
+ }
+
+ @Override
+ public boolean remove(K key, long version) {
+ boolean removed = map.remove(key, version);
+ if (removed) {
+ notifyListeners(mapName, REMOVE, key, null);
+ }
+ return removed;
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ boolean replaced = map.replace(key, oldValue, newValue);
+ if (replaced) {
+ notifyListeners(mapName, REMOVE, key, null);
+ }
+ return replaced;
+ }
+
+ @Override
+ public boolean replace(K key, long oldVersion, V newValue) {
+ boolean replaced = map.replace(key, map.get(key), newValue);
+ if (replaced) {
+ notifyListeners(mapName, REMOVE, key, null);
+ }
+ return replaced;
+ }
+
+ @Override
+ public void addListener(MapEventListener<K, V> listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(MapEventListener<K, V> listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public Map<K, V> asJavaMap() {
+ return map;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder<K, V> implements ConsistentMapBuilder<K, V> {
+ String mapName = "map";
+
+ @Override
+ public ConsistentMapBuilder<K, V> withName(String mapName) {
+ this.mapName = mapName;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withApplicationId(ApplicationId id) {
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withSerializer(Serializer serializer) {
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withPartitionsDisabled() {
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withUpdatesDisabled() {
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withPurgeOnUninstall() {
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withRelaxedReadConsistency() {
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withMeteringDisabled() {
+ return this;
+ }
+
+ @Override
+ public ConsistentMap<K, V> build() {
+ return new TestConsistentMap<>(mapName);
+ }
+
+ @Override
+ public AsyncConsistentMap<K, V> buildAsyncMap() {
+ return null;
+ }
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestEventuallyConsistentMap.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestEventuallyConsistentMap.java
new file mode 100644
index 00000000..4f612de2
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestEventuallyConsistentMap.java
@@ -0,0 +1,238 @@
+/*
+ * 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.store.service;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.Timestamp;
+
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.*;
+
+/**
+ * Testing version of an Eventually Consistent Map.
+ */
+
+public final class TestEventuallyConsistentMap<K, V> extends EventuallyConsistentMapAdapter<K, V> {
+
+ private final HashMap<K, V> map;
+ private final String mapName;
+ private final List<EventuallyConsistentMapListener<K, V>> listeners;
+ private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
+
+ private TestEventuallyConsistentMap(String mapName,
+ BiFunction<K, V, Collection<NodeId>> peerUpdateFunction) {
+ map = new HashMap<>();
+ listeners = new LinkedList<>();
+ this.mapName = mapName;
+ this.peerUpdateFunction = peerUpdateFunction;
+ }
+
+ /**
+ * Notify all listeners of an event.
+ */
+ private void notifyListeners(EventuallyConsistentMapEvent<K, V> event) {
+ listeners.forEach(
+ listener -> listener.event(event)
+ );
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(V value) {
+ return map.containsValue(value);
+ }
+
+ @Override
+ public V get(K key) {
+ return map.get(key);
+ }
+
+ @Override
+ public void put(K key, V value) {
+ map.put(key, value);
+ EventuallyConsistentMapEvent<K, V> addEvent =
+ new EventuallyConsistentMapEvent<>(mapName, PUT, key, value);
+ notifyListeners(addEvent);
+ if (peerUpdateFunction != null) {
+ peerUpdateFunction.apply(key, value);
+ }
+ }
+
+ @Override
+ public V remove(K key) {
+ V result = map.remove(key);
+ if (result != null) {
+ EventuallyConsistentMapEvent<K, V> removeEvent =
+ new EventuallyConsistentMapEvent<>(mapName, REMOVE,
+ key, map.get(key));
+ notifyListeners(removeEvent);
+ }
+ return result;
+ }
+
+ @Override
+ public void remove(K key, V value) {
+ boolean removed = map.remove(key, value);
+ if (removed) {
+ EventuallyConsistentMapEvent<K, V> removeEvent =
+ new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, value);
+ notifyListeners(removeEvent);
+ }
+ }
+
+ @Override
+ public V compute(K key, BiFunction<K, V, V> recomputeFunction) {
+ return map.compute(key, recomputeFunction);
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ map.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ return map.values();
+ }
+
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ return map.entrySet();
+ }
+
+ public static <K, V> Builder<K, V> builder() {
+ return new Builder<>();
+ }
+
+ @Override
+ public void addListener(EventuallyConsistentMapListener<K, V> listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(EventuallyConsistentMapListener<K, V> listener) {
+ listeners.remove(listener);
+ }
+
+ public static class Builder<K, V> implements EventuallyConsistentMapBuilder<K, V> {
+ private String name;
+ private BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withSerializer(KryoNamespace.Builder serializerBuilder) {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V>
+ withTimestampProvider(BiFunction<K, V, Timestamp> timestampProvider) {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withEventExecutor(ExecutorService executor) {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withCommunicationExecutor(ExecutorService executor) {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withBackgroundExecutor(ScheduledExecutorService executor) {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V>
+ withPeerUpdateFunction(BiFunction<K, V, Collection<NodeId>> peerUpdateFunction) {
+ this.peerUpdateFunction = peerUpdateFunction;
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withTombstonesDisabled() {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withAntiEntropyPeriod(long period, TimeUnit unit) {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withFasterConvergence() {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withPersistence() {
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMap<K, V> build() {
+ if (name == null) {
+ name = "test";
+ }
+ return new TestEventuallyConsistentMap<>(name, peerUpdateFunction);
+ }
+ }
+
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestStorageService.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestStorageService.java
new file mode 100644
index 00000000..9b52a305
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/TestStorageService.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.store.service;
+
+public class TestStorageService extends StorageServiceAdapter {
+
+
+ @Override
+ public <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder() {
+ return TestEventuallyConsistentMap.builder();
+ }
+
+ @Override
+ public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
+ return TestConsistentMap.builder();
+ }
+
+ @Override
+ public <E> DistributedSetBuilder<E> setBuilder() {
+ throw new UnsupportedOperationException("setBuilder");
+ }
+
+ @Override
+ public <E> DistributedQueueBuilder<E> queueBuilder() {
+ throw new UnsupportedOperationException("queueBuilder");
+ }
+
+ @Override
+ public AtomicCounterBuilder atomicCounterBuilder() {
+ return TestAtomicCounter.builder();
+ }
+
+ @Override
+ public <V> AtomicValueBuilder<V> atomicValueBuilder() {
+ throw new UnsupportedOperationException("atomicValueBuilder");
+ }
+
+ @Override
+ public TransactionContextBuilder transactionContextBuilder() {
+ throw new UnsupportedOperationException("transactionContextBuilder");
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/VersionedTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/VersionedTest.java
new file mode 100644
index 00000000..8a401db2
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/VersionedTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.store.service;
+
+import com.google.common.testing.EqualsTester;
+import junit.framework.TestCase;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Versioned unit tests.
+ */
+public class VersionedTest extends TestCase {
+
+ private final Versioned<Integer> stats1 = new Versioned<>(1, 2, 3);
+
+ private final Versioned<Integer> stats2 = new Versioned<>(1, 2);
+
+ /**
+ * Tests the creation of the MapEvent object.
+ */
+ @Test
+ public void testConstruction() {
+ assertThat(stats1.value(), is(1));
+ assertThat(stats1.version(), is(2L));
+ assertThat(stats1.creationTime(), is(3L));
+ }
+
+ /**
+ * Maps an Integer to a String - Utility function to test the map function.
+ * @param a Actual Integer parameter.
+ * @return String Mapped valued.
+ */
+ public static String transform(Integer a) {
+ return Integer.toString(a);
+ }
+
+ /**
+ * Tests the map function.
+ */
+ @Test
+ public void testMap() {
+ Versioned<String> tempObj = stats1.<String>map(VersionedTest::transform);
+ assertThat(tempObj.value(), is("1"));
+ }
+
+ /**
+ * Tests the valueOrElse method.
+ */
+ @Test
+ public void testOrElse() {
+ Versioned<String> vv = new Versioned<>("foo", 1);
+ Versioned<String> nullVV = null;
+ assertThat(Versioned.valueOrElse(vv, "bar"), is("foo"));
+ assertThat(Versioned.valueOrElse(nullVV, "bar"), is("bar"));
+ }
+
+ /**
+ * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(stats1, stats1)
+ .addEqualityGroup(stats2)
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/WallClockTimestampTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/WallClockTimestampTest.java
new file mode 100644
index 00000000..97f5a39b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/store/service/WallClockTimestampTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.store.service;
+
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onosproject.store.Timestamp;
+import org.onlab.util.KryoNamespace;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Tests for {@link WallClockTimestamp}.
+ */
+public class WallClockTimestampTest {
+
+ @Test
+ public final void testBasic() throws InterruptedException {
+ WallClockTimestamp ts1 = new WallClockTimestamp();
+ Thread.sleep(50);
+ WallClockTimestamp ts2 = new WallClockTimestamp();
+ long stamp = System.currentTimeMillis() + 10000;
+ WallClockTimestamp ts3 = new WallClockTimestamp(stamp);
+
+
+ assertTrue(ts1.compareTo(ts1) == 0);
+ assertTrue(ts2.compareTo(ts1) > 0);
+ assertTrue(ts1.compareTo(ts2) < 0);
+ assertTrue(ts3.unixTimestamp() == stamp);
+ }
+
+ @Test
+ public final void testKryoSerializable() {
+ WallClockTimestamp ts1 = new WallClockTimestamp();
+ WallClockTimestamp ts2 = new WallClockTimestamp(System.currentTimeMillis() + 10000);
+ final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+ final KryoNamespace kryos = KryoNamespace.newBuilder()
+ .register(WallClockTimestamp.class)
+ .build();
+
+ kryos.serialize(ts1, buffer);
+ buffer.flip();
+ Timestamp copy = kryos.deserialize(buffer);
+
+ new EqualsTester()
+ .addEqualityGroup(ts1, copy)
+ .addEqualityGroup(ts2)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionServiceAdapter.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionServiceAdapter.java
new file mode 100644
index 00000000..68d9264c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionServiceAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ui;
+
+import java.util.List;
+
+/**
+ * Adapter for testing against UI extension service.
+ */
+public class UiExtensionServiceAdapter implements UiExtensionService {
+ @Override
+ public void register(UiExtension extension) {
+ }
+
+ @Override
+ public void unregister(UiExtension extension) {
+ }
+
+ @Override
+ public List<UiExtension> getExtensions() {
+ return null;
+ }
+
+ @Override
+ public UiExtension getViewExtension(String viewId) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java
new file mode 100644
index 00000000..bd3d15f6
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.ui;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.junit.Assert.*;
+import static org.onosproject.ui.UiView.Category.OTHER;
+
+/**
+ * Tests the default user interface extension descriptor.
+ */
+public class UiExtensionTest {
+
+ private static final String FOO_ID = "foo";
+ private static final String FOO_LABEL = "Foo View";
+ private static final String BAR_ID = "bar";
+ private static final String BAR_LABEL = "Bar View";
+ private static final String CUSTOM = "custom";
+
+
+ private static final UiView FOO_VIEW = new UiView(OTHER, FOO_ID, FOO_LABEL);
+ private static final UiView BAR_VIEW = new UiView(OTHER, BAR_ID, BAR_LABEL);
+ private static final UiView HIDDEN_VIEW = new UiViewHidden(FOO_ID);
+
+ private static final UiMessageHandlerFactory MH_FACTORY = () -> null;
+ private static final UiTopoOverlayFactory TO_FACTORY = () -> null;
+
+ private final ClassLoader cl = getClass().getClassLoader();
+
+ private List<UiView> viewList;
+ private UiExtension ext;
+ private String css;
+ private String js;
+ private UiView view;
+
+
+
+ @Test
+ public void basics() throws IOException {
+ viewList = ImmutableList.of(FOO_VIEW);
+ ext = new UiExtension.Builder(cl, viewList).build();
+
+ css = new String(toByteArray(ext.css()));
+ assertTrue("incorrect css stream", css.contains("foo-css"));
+ js = new String(toByteArray(ext.js()));
+ assertTrue("incorrect js stream", js.contains("foo-js"));
+
+ assertEquals("expected 1 view", 1, ext.views().size());
+ view = ext.views().get(0);
+ assertEquals("wrong view category", OTHER, view.category());
+ assertEquals("wrong view id", FOO_ID, view.id());
+ assertEquals("wrong view label", FOO_LABEL, view.label());
+
+ assertNull("unexpected message handler factory", ext.messageHandlerFactory());
+ assertNull("unexpected topo overlay factory", ext.topoOverlayFactory());
+ }
+
+ @Test
+ public void withPath() throws IOException {
+ viewList = ImmutableList.of(FOO_VIEW);
+ ext = new UiExtension.Builder(cl, viewList)
+ .resourcePath(CUSTOM)
+ .build();
+
+ css = new String(toByteArray(ext.css()));
+ assertTrue("incorrect css stream", css.contains("custom-css"));
+ js = new String(toByteArray(ext.js()));
+ assertTrue("incorrect js stream", js.contains("custom-js"));
+
+ assertEquals("expected 1 view", 1, ext.views().size());
+ view = ext.views().get(0);
+ assertEquals("wrong view category", OTHER, view.category());
+ assertEquals("wrong view id", FOO_ID, view.id());
+ assertEquals("wrong view label", FOO_LABEL, view.label());
+
+ assertNull("unexpected message handler factory", ext.messageHandlerFactory());
+ assertNull("unexpected topo overlay factory", ext.topoOverlayFactory());
+ }
+
+ @Test
+ public void messageHandlerFactory() {
+ viewList = ImmutableList.of(FOO_VIEW);
+ ext = new UiExtension.Builder(cl, viewList)
+ .messageHandlerFactory(MH_FACTORY)
+ .build();
+
+ assertEquals("wrong message handler factory", MH_FACTORY,
+ ext.messageHandlerFactory());
+ assertNull("unexpected topo overlay factory", ext.topoOverlayFactory());
+ }
+
+ @Test
+ public void topoOverlayFactory() {
+ viewList = ImmutableList.of(HIDDEN_VIEW);
+ ext = new UiExtension.Builder(cl, viewList)
+ .topoOverlayFactory(TO_FACTORY)
+ .build();
+
+ assertNull("unexpected message handler factory", ext.messageHandlerFactory());
+ assertEquals("wrong topo overlay factory", TO_FACTORY,
+ ext.topoOverlayFactory());
+ }
+
+ @Test
+ public void twoViews() {
+ viewList = ImmutableList.of(FOO_VIEW, BAR_VIEW);
+ ext = new UiExtension.Builder(cl, viewList).build();
+
+ assertEquals("expected 2 views", 2, ext.views().size());
+
+ view = ext.views().get(0);
+ assertEquals("wrong view category", OTHER, view.category());
+ assertEquals("wrong view id", FOO_ID, view.id());
+ assertEquals("wrong view label", FOO_LABEL, view.label());
+
+ view = ext.views().get(1);
+ assertEquals("wrong view category", OTHER, view.category());
+ assertEquals("wrong view id", BAR_ID, view.id());
+ assertEquals("wrong view label", BAR_LABEL, view.label());
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java
new file mode 100644
index 00000000..7524bcb3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.ui.table;
+
+import org.junit.Test;
+import org.onosproject.ui.table.TableModel.SortDir;
+import org.onosproject.ui.table.cell.DefaultCellFormatter;
+import org.onosproject.ui.table.cell.HexFormatter;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link TableModel}.
+ */
+public class TableModelTest {
+
+ private static final String UNEX_SORT = "unexpected sort: index ";
+
+ private static final String FOO = "foo";
+ private static final String BAR = "bar";
+ private static final String ZOO = "zoo";
+
+ private enum StarWars {
+ LUKE_SKYWALKER, LEIA_ORGANA, HAN_SOLO, C3PO, R2D2, JABBA_THE_HUTT
+ }
+
+ private static class ParenFormatter implements CellFormatter {
+ @Override
+ public String format(Object value) {
+ return "(" + value + ")";
+ }
+ }
+
+ private TableModel tm;
+ private TableModel.Row[] rows;
+ private TableModel.Row row;
+ private CellFormatter fmt;
+
+ @Test(expected = NullPointerException.class)
+ public void guardAgainstNull() {
+ tm = new TableModel(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void guardAgainstEmpty() {
+ tm = new TableModel();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void guardAgainstDuplicateCols() {
+ tm = new TableModel(FOO, BAR, FOO);
+ }
+
+ @Test
+ public void basic() {
+ tm = new TableModel(FOO, BAR);
+ assertEquals("column count", 2, tm.columnCount());
+ assertEquals("row count", 0, tm.rowCount());
+ }
+
+ @Test
+ public void defaultFormatter() {
+ tm = new TableModel(FOO);
+ fmt = tm.getFormatter(FOO);
+ assertTrue("Wrong formatter", fmt instanceof DefaultCellFormatter);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void formatterBadColumn() {
+ tm = new TableModel(FOO);
+ fmt = tm.getFormatter(BAR);
+ }
+
+ @Test
+ public void altFormatter() {
+ tm = new TableModel(FOO, BAR);
+ tm.setFormatter(BAR, new ParenFormatter());
+
+ fmt = tm.getFormatter(FOO);
+ assertTrue("Wrong formatter", fmt instanceof DefaultCellFormatter);
+ assertEquals("Wrong result", "2", fmt.format(2));
+
+ fmt = tm.getFormatter(BAR);
+ assertTrue("Wrong formatter", fmt instanceof ParenFormatter);
+ assertEquals("Wrong result", "(2)", fmt.format(2));
+ }
+
+ @Test
+ public void emptyRow() {
+ tm = new TableModel(FOO, BAR);
+ tm.addRow();
+ assertEquals("bad row count", 1, tm.rowCount());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void rowBadColumn() {
+ tm = new TableModel(FOO, BAR);
+ tm.addRow().cell(ZOO, 2);
+ }
+
+ @Test
+ public void simpleRow() {
+ tm = new TableModel(FOO, BAR);
+ tm.addRow().cell(FOO, 3).cell(BAR, true);
+ assertEquals("bad row count", 1, tm.rowCount());
+ row = tm.getRows()[0];
+ assertEquals("bad cell", 3, row.get(FOO));
+ assertEquals("bad cell", true, row.get(BAR));
+ }
+
+
+ private static final String ONE = "one";
+ private static final String TWO = "two";
+ private static final String THREE = "three";
+ private static final String FOUR = "four";
+ private static final String ELEVEN = "eleven";
+ private static final String TWELVE = "twelve";
+ private static final String TWENTY = "twenty";
+ private static final String THIRTY = "thirty";
+
+ private static final String[] NAMES = {
+ FOUR,
+ THREE,
+ TWO,
+ ONE,
+ ELEVEN,
+ TWELVE,
+ THIRTY,
+ TWENTY,
+ };
+ private static final String[] SORTED_NAMES = {
+ ELEVEN,
+ FOUR,
+ ONE,
+ THIRTY,
+ THREE,
+ TWELVE,
+ TWENTY,
+ TWO,
+ };
+
+ private static final int[] NUMBERS = {
+ 4, 3, 2, 1, 11, 12, 30, 20
+ };
+
+ private static final int[] SORTED_NUMBERS = {
+ 1, 2, 3, 4, 11, 12, 20, 30
+ };
+
+ private static final String[] SORTED_HEX = {
+ "0x1", "0x2", "0x3", "0x4", "0xb", "0xc", "0x14", "0x1e"
+ };
+
+ @Test
+ public void verifyTestData() {
+ // not a unit test per se, but will fail if we don't keep
+ // the three test arrays in sync
+ int nalen = NAMES.length;
+ int snlen = SORTED_NAMES.length;
+ int nulen = NUMBERS.length;
+
+ if (nalen != snlen || nalen != nulen) {
+ fail("test data array size discrepancy");
+ }
+ }
+
+ private void initUnsortedTable() {
+ tm = new TableModel(FOO, BAR);
+ for (int i = 0; i < NAMES.length; i++) {
+ tm.addRow().cell(FOO, NAMES[i]).cell(BAR, NUMBERS[i]);
+ }
+ }
+
+ @Test
+ public void tableStringSort() {
+ initUnsortedTable();
+
+ // sort by name
+ tm.sort(FOO, SortDir.ASC);
+
+ // verify results
+ rows = tm.getRows();
+ int nr = rows.length;
+ assertEquals("row count", NAMES.length, nr);
+ for (int i = 0; i < nr; i++) {
+ assertEquals(UNEX_SORT + i, SORTED_NAMES[i], rows[i].get(FOO));
+ }
+
+ // now the other way
+ tm.sort(FOO, SortDir.DESC);
+
+ // verify results
+ rows = tm.getRows();
+ nr = rows.length;
+ assertEquals("row count", NAMES.length, nr);
+ for (int i = 0; i < nr; i++) {
+ assertEquals(UNEX_SORT + i,
+ SORTED_NAMES[nr - 1 - i], rows[i].get(FOO));
+ }
+ }
+
+ @Test
+ public void tableNumberSort() {
+ initUnsortedTable();
+
+ // sort by number
+ tm.sort(BAR, SortDir.ASC);
+
+ // verify results
+ rows = tm.getRows();
+ int nr = rows.length;
+ assertEquals("row count", NUMBERS.length, nr);
+ for (int i = 0; i < nr; i++) {
+ assertEquals(UNEX_SORT + i, SORTED_NUMBERS[i], rows[i].get(BAR));
+ }
+
+ // now the other way
+ tm.sort(BAR, SortDir.DESC);
+
+ // verify results
+ rows = tm.getRows();
+ nr = rows.length;
+ assertEquals("row count", NUMBERS.length, nr);
+ for (int i = 0; i < nr; i++) {
+ assertEquals(UNEX_SORT + i,
+ SORTED_NUMBERS[nr - 1 - i], rows[i].get(BAR));
+ }
+ }
+
+ @Test
+ public void sortAndFormat() {
+ initUnsortedTable();
+
+ // set hex formatter
+ tm.setFormatter(BAR, HexFormatter.INSTANCE);
+
+ // sort by number
+ tm.sort(BAR, SortDir.ASC);
+
+ // verify results
+ rows = tm.getRows();
+ int nr = rows.length;
+ assertEquals("row count", SORTED_HEX.length, nr);
+ for (int i = 0; i < nr; i++) {
+ assertEquals(UNEX_SORT + i, SORTED_HEX[i], rows[i].getAsString(BAR));
+ }
+ }
+
+ private static final String[][] SORTED_NAMES_AND_HEX = {
+ {ELEVEN, "0xb"},
+ {FOUR, "0x4"},
+ {ONE, "0x1"},
+ {THIRTY, "0x1e"},
+ {THREE, "0x3"},
+ {TWELVE, "0xc"},
+ {TWENTY, "0x14"},
+ {TWO, "0x2"},
+ };
+
+ @Test
+ public void sortAndFormatTwo() {
+ initUnsortedTable();
+ tm.setFormatter(BAR, HexFormatter.INSTANCE);
+ tm.sort(FOO, SortDir.ASC);
+ rows = tm.getRows();
+ int nr = rows.length;
+ for (int i = 0; i < nr; i++) {
+ String[] exp = SORTED_NAMES_AND_HEX[i];
+ String[] act = rows[i].getAsFormattedStrings();
+ assertArrayEquals(UNEX_SORT + i, exp, act);
+ }
+ }
+
+ private static final String[] FBZ = {FOO, BAR, ZOO};
+
+ @Test
+ public void getColumnIds() {
+ tm = new TableModel(FOO, BAR, ZOO);
+ assertArrayEquals("col IDs", FBZ, tm.getColumnIds());
+ }
+
+ @Test
+ public void sortDirAsc() {
+ assertEquals("asc sort dir", SortDir.ASC, TableModel.sortDir("asc"));
+ }
+
+ @Test
+ public void sortDirDesc() {
+ assertEquals("desc sort dir", SortDir.DESC, TableModel.sortDir("desc"));
+ }
+
+ @Test
+ public void sortDirOther() {
+ assertEquals("other sort dir", SortDir.ASC, TableModel.sortDir("other"));
+ }
+
+ @Test
+ public void sortDirNull() {
+ assertEquals("null sort dir", SortDir.ASC, TableModel.sortDir(null));
+ }
+
+
+ @Test
+ public void enumSort() {
+ tm = new TableModel(FOO);
+ tm.addRow().cell(FOO, StarWars.HAN_SOLO);
+ tm.addRow().cell(FOO, StarWars.C3PO);
+ tm.addRow().cell(FOO, StarWars.JABBA_THE_HUTT);
+ tm.addRow().cell(FOO, StarWars.LEIA_ORGANA);
+ tm.addRow().cell(FOO, StarWars.R2D2);
+ tm.addRow().cell(FOO, StarWars.LUKE_SKYWALKER);
+
+ tm.sort(FOO, SortDir.ASC);
+
+ // verify expected results
+ StarWars[] ordered = StarWars.values();
+ TableModel.Row[] rows = tm.getRows();
+ assertEquals("wrong length?", ordered.length, rows.length);
+ int nr = rows.length;
+ for (int i = 0; i < nr; i++) {
+ assertEquals(UNEX_SORT + i, ordered[i], rows[i].get(FOO));
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java
new file mode 100644
index 00000000..7c2f2d73
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.table;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link TableUtils}.
+ */
+public class TableUtilsTest {
+
+ private static final String FOO = "foo";
+ private static final String BAR = "bar";
+
+ private static final String ARRAY_AS_STRING =
+ "[{\"foo\":\"1\",\"bar\":\"2\"},{\"foo\":\"3\",\"bar\":\"4\"}]";
+
+ @Test
+ public void basic() {
+ TableModel tm = new TableModel(FOO, BAR);
+ tm.addRow().cell(FOO, 1).cell(BAR, 2);
+ tm.addRow().cell(FOO, 3).cell(BAR, 4);
+
+ ArrayNode array = TableUtils.generateArrayNode(tm);
+ Assert.assertEquals("wrong results", ARRAY_AS_STRING, array.toString());
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellComparatorTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellComparatorTest.java
new file mode 100644
index 00000000..0b9af23d
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellComparatorTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.ui.table.CellComparator;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link AbstractCellComparator}.
+ */
+public class AbstractCellComparatorTest {
+
+ private static class Concrete extends AbstractCellComparator {
+ @Override
+ protected int nonNullCompare(Object o1, Object o2) {
+ return 42;
+ }
+ }
+
+ private CellComparator cmp = new Concrete();
+
+ @Test
+ public void twoNullArgs() {
+ assertTrue("two nulls", cmp.compare(null, null) == 0);
+ }
+
+ @Test
+ public void nullArgOne() {
+ assertTrue("null one", cmp.compare(null, 1) < 0);
+ }
+
+ @Test
+ public void nullArgTwo() {
+ assertTrue("null two", cmp.compare(1, null) > 0);
+ }
+
+ // mock output, but check that our method was invoked...
+ @Test
+ public void noNulls() {
+ assertTrue("no Nulls", cmp.compare(1, 2) == 42);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellFormatterTest.java
new file mode 100644
index 00000000..7a3c34bc
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AbstractCellFormatterTest.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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link AbstractCellFormatter}.
+ */
+public class AbstractCellFormatterTest {
+
+ private static final String MOCK_OUTPUT = "Mock!!";
+
+ private static class Concrete extends AbstractCellFormatter {
+ @Override
+ protected String nonNullFormat(Object value) {
+ return MOCK_OUTPUT;
+ }
+ }
+
+ private CellFormatter frm = new Concrete();
+
+ @Test
+ public void nullInput() {
+ assertEquals("wrong result", "", frm.format(null));
+ }
+
+ // mock output, but check that our method was invoked...
+ @Test
+ public void nonNullInput() {
+ assertEquals("what?", MOCK_OUTPUT, frm.format(1));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AppIdFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AppIdFormatterTest.java
new file mode 100644
index 00000000..e74fb47c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/AppIdFormatterTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link AppIdFormatter}.
+ */
+public class AppIdFormatterTest {
+
+ private static final ApplicationId APP_ID = new ApplicationId() {
+ @Override
+ public short id() {
+ return 25;
+ }
+
+ @Override
+ public String name() {
+ return "some app";
+ }
+ };
+
+ private CellFormatter fmt = AppIdFormatter.INSTANCE;
+
+ @Test
+ public void basic() {
+ assertEquals("wrong format", "25 : some app", fmt.format(APP_ID));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/ConnectPointFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/ConnectPointFormatterTest.java
new file mode 100644
index 00000000..65fd7843
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/ConnectPointFormatterTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link ConnectPointFormatter}.
+ */
+public class ConnectPointFormatterTest {
+
+ private static final DeviceId DEVID = DeviceId.deviceId("foobar");
+ private static final PortNumber PORT = PortNumber.portNumber(42);
+
+ private static final ConnectPoint CP = new ConnectPoint(DEVID, PORT);
+
+ private CellFormatter fmt = ConnectPointFormatter.INSTANCE;
+
+ @Test
+ public void basic() {
+ assertEquals("wrong format", "foobar/42", fmt.format(CP));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellComparatorTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellComparatorTest.java
new file mode 100644
index 00000000..87c95288
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellComparatorTest.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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.ui.table.CellComparator;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link DefaultCellComparator}.
+ */
+public class DefaultCellComparatorTest {
+
+ private static final String SOME = "SoMeStRiNg";
+ private static final String OTHER = "OtherSTRING";
+
+ private CellComparator cmp = DefaultCellComparator.INSTANCE;
+
+ // default comparator should detect Comparable<T> impls and use that
+
+ @Test
+ public void sameString() {
+ assertTrue("same string", cmp.compare(SOME, SOME) == 0);
+ }
+
+ @Test
+ public void someVsOther() {
+ assertTrue("some vs other", cmp.compare(SOME, OTHER) > 0);
+ }
+
+ @Test
+ public void otherVsSome() {
+ assertTrue("other vs some", cmp.compare(OTHER, SOME) < 0);
+ }
+
+ @Test
+ public void someVsNull() {
+ assertTrue("some vs null", cmp.compare(SOME, null) > 0);
+ }
+
+ @Test
+ public void nullVsSome() {
+ assertTrue("null vs some", cmp.compare(null, SOME) < 0);
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void mismatch() {
+ cmp.compare(42, SOME);
+ }
+
+
+ @Test
+ public void strElevenTwo() {
+ assertTrue("str 11 vs 2", cmp.compare("11", "2") < 0);
+ }
+
+ @Test
+ public void intElevenTwo() {
+ assertTrue("int 11 vs 2", cmp.compare(11, 2) > 0);
+ }
+
+
+ @Test
+ public void intSmallBig() {
+ assertTrue("int 2 vs 4", cmp.compare(2, 4) < 0);
+ }
+
+ @Test
+ public void intBigSmall() {
+ assertTrue("int 4 vs 2", cmp.compare(4, 2) > 0);
+ }
+
+ @Test
+ public void intEqual() {
+ assertTrue("int 4 vs 4", cmp.compare(4, 4) == 0);
+ }
+
+ @Test
+ public void longSmallBig() {
+ assertTrue("long 2 vs 4", cmp.compare(2L, 4L) < 0);
+ }
+
+ @Test
+ public void longBigSmall() {
+ assertTrue("long 4 vs 2", cmp.compare(4L, 2L) > 0);
+ }
+
+ @Test
+ public void longEqual() {
+ assertTrue("long 4 vs 4", cmp.compare(4L, 4L) == 0);
+ }
+
+
+ private enum SmallStarWars { C3PO, R2D2, LUKE }
+
+ @Test
+ public void swEpisodeI() {
+ assertTrue("c3po r2d2",
+ cmp.compare(SmallStarWars.C3PO, SmallStarWars.R2D2) < 0);
+ }
+
+ @Test
+ public void swEpisodeII() {
+ assertTrue("r2d2 c3po",
+ cmp.compare(SmallStarWars.R2D2, SmallStarWars.C3PO) > 0);
+ }
+
+ @Test
+ public void swEpisodeIII() {
+ assertTrue("luke c3po",
+ cmp.compare(SmallStarWars.LUKE, SmallStarWars.C3PO) > 0);
+ }
+
+ @Test
+ public void swEpisodeIV() {
+ assertTrue("c3po luke",
+ cmp.compare(SmallStarWars.C3PO, SmallStarWars.LUKE) < 0);
+ }
+
+ @Test
+ public void swEpisodeV() {
+ assertTrue("luke r2d2",
+ cmp.compare(SmallStarWars.LUKE, SmallStarWars.R2D2) > 0);
+ }
+
+ @Test
+ public void swEpisodeVI() {
+ assertTrue("r2d2 luke",
+ cmp.compare(SmallStarWars.R2D2, SmallStarWars.LUKE) < 0);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellFormatterTest.java
new file mode 100644
index 00000000..6351a1f4
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/DefaultCellFormatterTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link DefaultCellFormatter}.
+ */
+public class DefaultCellFormatterTest {
+
+ private static final String UNEX = "Unexpected result";
+ private static final String SOME_STRING = "SoMeStRiNg";
+
+ private static class TestClass {
+ @Override
+ public String toString() {
+ return SOME_STRING;
+ }
+ }
+
+ private CellFormatter fmt = DefaultCellFormatter.INSTANCE;
+
+ @Test
+ public void formatNull() {
+ assertEquals(UNEX, "", fmt.format(null));
+ }
+
+ @Test
+ public void formatInteger() {
+ assertEquals(UNEX, "3", fmt.format(3));
+ }
+
+ @Test
+ public void formatTrue() {
+ assertEquals(UNEX, "true", fmt.format(true));
+ }
+
+ @Test
+ public void formatFalse() {
+ assertEquals(UNEX, "false", fmt.format(false));
+ }
+
+ @Test
+ public void formatString() {
+ assertEquals(UNEX, "FOO", fmt.format("FOO"));
+ }
+
+ @Test
+ public void formatObject() {
+ assertEquals(UNEX, SOME_STRING, fmt.format(new TestClass()));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/EnumFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/EnumFormatterTest.java
new file mode 100644
index 00000000..68833c90
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/EnumFormatterTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link EnumFormatter}.
+ */
+public class EnumFormatterTest {
+
+ enum TestEnum {
+ ADDED,
+ PENDING_ADD,
+ WAITING_AUDIT_COMPLETE
+ }
+
+ private CellFormatter fmt = EnumFormatter.INSTANCE;
+
+ @Test
+ public void nullValue() {
+ assertEquals("null value", "", fmt.format(null));
+ }
+
+ @Test
+ public void noUnderscores() {
+ assertEquals("All caps", "Added", fmt.format(TestEnum.ADDED));
+ }
+
+ @Test
+ public void underscores() {
+ assertEquals("All caps with underscores",
+ "Pending Add", fmt.format(TestEnum.PENDING_ADD));
+ }
+
+ @Test
+ public void multiUnderscores() {
+ assertEquals("All caps with underscores",
+ "Waiting Audit Complete",
+ fmt.format(TestEnum.WAITING_AUDIT_COMPLETE));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HexFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HexFormatterTest.java
new file mode 100644
index 00000000..ad23b02c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HexFormatterTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link HexFormatter}.
+ */
+public class HexFormatterTest {
+
+ private CellFormatter fmt = HexFormatter.INSTANCE;
+
+ @Test
+ public void nullValue() {
+ assertEquals("null value", "", fmt.format(null));
+ }
+
+ @Test
+ public void zero() {
+ assertEquals("zero", "0x0", fmt.format(0));
+ }
+
+ @Test
+ public void one() {
+ assertEquals("one", "0x1", fmt.format(1));
+ }
+
+ @Test
+ public void ten() {
+ assertEquals("ten", "0xa", fmt.format(10));
+ }
+
+ @Test
+ public void twenty() {
+ assertEquals("twenty", "0x14", fmt.format(20));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HostLocationFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HostLocationFormatterTest.java
new file mode 100644
index 00000000..bfbe4541
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/HostLocationFormatterTest.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.ui.table.cell;
+
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link HostLocationFormatter}.
+ */
+public class HostLocationFormatterTest {
+
+ private static final DeviceId DEVID = DeviceId.deviceId("foobar");
+ private static final PortNumber PORT = PortNumber.portNumber(42);
+ private static final long TIME = 12345;
+
+ private static final HostLocation LOC = new HostLocation(DEVID, PORT, TIME);
+
+ private CellFormatter fmt = HostLocationFormatter.INSTANCE;
+
+ @Test
+ public void basic() {
+ assertEquals("wrong format", "foobar/42", fmt.format(LOC));
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/TimeFormatterTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/TimeFormatterTest.java
new file mode 100644
index 00000000..f41d82b3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/table/cell/TimeFormatterTest.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.ui.table.cell;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.junit.Test;
+import org.onosproject.ui.table.CellFormatter;
+
+import java.util.Locale;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link TimeFormatter}.
+ */
+public class TimeFormatterTest {
+
+ private static final Locale LOCALE = Locale.ENGLISH;
+ private static final DateTimeZone ZONE = DateTimeZone.UTC;
+
+ private static final DateTime TIME = new DateTime(2015, 5, 4, 15, 30, ZONE);
+ private static final String EXP_ZONE_NAME = "3:30:00 PM UTC";
+ private static final String EXP_ZONE_OFFSET = "3:30:00 PM +00:00";
+
+ // Have to use explicit Locale and TimeZone for the unit test, so that
+ // irrespective of which locale and time zone the test is run in, it
+ // always produces the same result...
+ private CellFormatter fmt =
+ new TimeFormatter().withLocale(LOCALE).withZone(ZONE);
+
+ @Test
+ public void basic() {
+ assertTrue("wrong format", (EXP_ZONE_NAME.equals(fmt.format(TIME)) ||
+ EXP_ZONE_OFFSET.equals(fmt.format(TIME))));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkMapTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkMapTest.java
new file mode 100644
index 00000000..17fcc229
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkMapTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.ui.topo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link BiLinkMap}.
+ */
+public class BiLinkMapTest extends BiLinkTestBase {
+
+
+ private ConcreteLink clink;
+ private ConcreteLinkMap linkMap;
+
+ @Before
+ public void setUp() {
+ linkMap = new ConcreteLinkMap();
+ }
+
+ @Test
+ public void basic() {
+ assertEquals("wrong map size", 0, linkMap.size());
+ assertTrue("unexpected links", linkMap.biLinks().isEmpty());
+ }
+
+ @Test
+ public void addSameLinkTwice() {
+ linkMap.add(LINK_AB);
+ assertEquals("wrong map size", 1, linkMap.size());
+ clink = linkMap.biLinks().iterator().next();
+ assertEquals("wrong link one", LINK_AB, clink.one());
+ assertNull("unexpected link two", clink.two());
+
+ linkMap.add(LINK_AB);
+ assertEquals("wrong map size", 1, linkMap.size());
+ clink = linkMap.biLinks().iterator().next();
+ assertEquals("wrong link one", LINK_AB, clink.one());
+ assertNull("unexpected link two", clink.two());
+ }
+
+ @Test
+ public void addPairOfLinks() {
+ linkMap.add(LINK_AB);
+ assertEquals("wrong map size", 1, linkMap.size());
+ clink = linkMap.biLinks().iterator().next();
+ assertEquals("wrong link one", LINK_AB, clink.one());
+ assertNull("unexpected link two", clink.two());
+
+ linkMap.add(LINK_BA);
+ assertEquals("wrong map size", 1, linkMap.size());
+ clink = linkMap.biLinks().iterator().next();
+ assertEquals("wrong link one", LINK_AB, clink.one());
+ assertEquals("wrong link two", LINK_BA, clink.two());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java
new file mode 100644
index 00000000..1acc06fa
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ui.topo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests for {@link BiLink}.
+ */
+public class BiLinkTest extends BiLinkTestBase {
+
+ private static final String EXP_ID_AB = "device-a/1-device-b/2";
+
+ private BiLink blink;
+
+ @Test
+ public void basic() {
+ blink = new ConcreteLink(KEY_AB, LINK_AB);
+ assertEquals("wrong id", EXP_ID_AB, blink.linkId());
+ assertEquals("wrong key", KEY_AB, blink.key());
+ assertEquals("wrong link one", LINK_AB, blink.one());
+ assertNull("what?", blink.two());
+
+ blink.setOther(LINK_BA);
+ assertEquals("wrong link two", LINK_BA, blink.two());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullKey() {
+ new ConcreteLink(null, LINK_AB);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullLink() {
+ new ConcreteLink(KEY_AB, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullOther() {
+ blink = new ConcreteLink(KEY_AB, LINK_AB);
+ blink.setOther(null);
+ }
+}
+
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java
new file mode 100644
index 00000000..b5bd41ef
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java
@@ -0,0 +1,98 @@
+/*
+ * 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.ui.topo;
+
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Base class for unit tests of {@link BiLink} and {@link BiLinkMap}.
+ */
+public abstract class BiLinkTestBase {
+
+ protected static class FakeLink implements Link {
+ private final ConnectPoint src;
+ private final ConnectPoint dst;
+
+ FakeLink(ConnectPoint src, ConnectPoint dst) {
+ this.src = src;
+ this.dst = dst;
+ }
+
+ @Override public ConnectPoint src() {
+ return src;
+ }
+ @Override public ConnectPoint dst() {
+ return dst;
+ }
+
+ @Override public Type type() {
+ return null;
+ }
+ @Override public State state() {
+ return null;
+ }
+ @Override public boolean isDurable() {
+ return false;
+ }
+ @Override public Annotations annotations() {
+ return null;
+ }
+ @Override public ProviderId providerId() {
+ return null;
+ }
+ }
+
+ protected static final DeviceId DEV_A_ID = DeviceId.deviceId("device-A");
+ protected static final DeviceId DEV_B_ID = DeviceId.deviceId("device-B");
+ protected static final PortNumber PORT_1 = PortNumber.portNumber(1);
+ protected static final PortNumber PORT_2 = PortNumber.portNumber(2);
+
+ protected static final ConnectPoint CP_A1 = new ConnectPoint(DEV_A_ID, PORT_1);
+ protected static final ConnectPoint CP_B2 = new ConnectPoint(DEV_B_ID, PORT_2);
+
+ protected static final LinkKey KEY_AB = LinkKey.linkKey(CP_A1, CP_B2);
+ protected static final LinkKey KEY_BA = LinkKey.linkKey(CP_B2, CP_A1);
+
+ protected static final Link LINK_AB = new FakeLink(CP_A1, CP_B2);
+ protected static final Link LINK_BA = new FakeLink(CP_B2, CP_A1);
+
+ protected static class ConcreteLink extends BiLink {
+ public ConcreteLink(LinkKey key, Link link) {
+ super(key, link);
+ }
+ @Override
+ public LinkHighlight highlight(Enum<?> type) {
+ return null;
+ }
+ }
+
+ protected static class ConcreteLinkMap extends BiLinkMap<ConcreteLink> {
+ @Override
+ public ConcreteLink create(LinkKey key, Link link) {
+ return new ConcreteLink(key, link);
+ }
+ }
+
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ButtonIdTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ButtonIdTest.java
new file mode 100644
index 00000000..04c6dc18
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ButtonIdTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ui.topo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link ButtonId}.
+ */
+public class ButtonIdTest {
+
+ private static final String ID1 = "id-1";
+ private static final String ID2 = "id-2";
+
+ private ButtonId b1, b2;
+
+
+ @Test
+ public void basic() {
+ b1 = new ButtonId(ID1);
+ }
+
+ @Test
+ public void same() {
+ b1 = new ButtonId(ID1);
+ b2 = new ButtonId(ID1);
+ assertFalse("same ref?", b1 == b2);
+ assertTrue("not equal?", b1.equals(b2));
+ }
+
+ @Test
+ public void notSame() {
+ b1 = new ButtonId(ID1);
+ b2 = new ButtonId(ID2);
+ assertFalse("same ref?", b1 == b2);
+ assertFalse("equal?", b1.equals(b2));
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java
new file mode 100644
index 00000000..7d6dfe67
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ui.topo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.ui.topo.Highlights.Amount;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link Highlights}.
+ */
+public class HighlightsTest {
+
+ private static final String DEV_1 = "dev-1";
+ private static final String DEV_2 = "dev-2";
+ private static final String HOST_A = "Host...A";
+
+ private Highlights highlights;
+ private DeviceHighlight dh1;
+ private DeviceHighlight dh2;
+ private HostHighlight hha;
+
+ @Before
+ public void setUp() {
+ highlights = new Highlights();
+ }
+
+ @Test
+ public void basic() {
+ assertEquals("devices", 0, highlights.devices().size());
+ assertEquals("hosts", 0, highlights.hosts().size());
+ assertEquals("links", 0, highlights.links().size());
+ assertEquals("sudue", Amount.ZERO, highlights.subdueLevel());
+ }
+
+ @Test
+ public void coupleOfDevices() {
+ dh1 = new DeviceHighlight(DEV_1);
+ dh2 = new DeviceHighlight(DEV_2);
+
+ highlights.add(dh1);
+ highlights.add(dh2);
+ assertTrue("missing dh1", highlights.devices().contains(dh1));
+ assertTrue("missing dh2", highlights.devices().contains(dh2));
+ }
+
+ @Test
+ public void alternateSubdue() {
+ highlights.subdueAllElse(Amount.MINIMALLY);
+ assertEquals("wrong level", Amount.MINIMALLY, highlights.subdueLevel());
+ }
+
+ @Test
+ public void highlightRetrieval() {
+ dh1 = new DeviceHighlight(DEV_1);
+ hha = new HostHighlight(HOST_A);
+ highlights.add(dh1)
+ .add(hha);
+
+ assertNull("dev as host", highlights.getHost(DEV_1));
+ assertNull("host as dev", highlights.getDevice(HOST_A));
+
+ assertEquals("missed dev as dev", dh1, highlights.getDevice(DEV_1));
+ assertEquals("missed dev as node", dh1, highlights.getNode(DEV_1));
+
+ assertEquals("missed host as host", hha, highlights.getHost(HOST_A));
+ assertEquals("missed host as node", hha, highlights.getNode(HOST_A));
+ }
+
+ // NOTE: further unit tests involving the Highlights class are done
+ // in TopoJsonTest.
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/LinkHighlightTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/LinkHighlightTest.java
new file mode 100644
index 00000000..205f08ce
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/LinkHighlightTest.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.ui.topo;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.*;
+
+/**
+ * Unit tests for {@link LinkHighlight}.
+ */
+public class LinkHighlightTest {
+
+ private static final String LINK_ID = "link-id-for-testing";
+ private static final String LABEL = "some label";
+ private static final String EMPTY = "";
+ private static final String CUSTOM = "custom";
+ private static final String ANIMATED = "animated";
+ private static final String OPTICAL = "optical";
+
+ private LinkHighlight lh;
+
+ @Test
+ public void basic() {
+ lh = new LinkHighlight(LINK_ID, NO_HIGHLIGHT);
+
+ assertEquals("wrong flavor", NO_HIGHLIGHT, lh.flavor());
+ assertTrue("unexpected mods", lh.mods().isEmpty());
+ assertEquals("wrong css", "plain", lh.cssClasses());
+ assertEquals("wrong label", EMPTY, lh.label());
+ }
+
+ @Test
+ public void primaryOptical() {
+ lh = new LinkHighlight(LINK_ID, PRIMARY_HIGHLIGHT)
+ .addMod(LinkHighlight.MOD_OPTICAL);
+
+ assertEquals("wrong flavor", PRIMARY_HIGHLIGHT, lh.flavor());
+ assertEquals("missing mod", 1, lh.mods().size());
+ Mod m = lh.mods().iterator().next();
+ assertEquals("wrong mod", LinkHighlight.MOD_OPTICAL, m);
+ assertEquals("wrong css", "primary optical", lh.cssClasses());
+ assertEquals("wrong label", EMPTY, lh.label());
+ }
+
+ @Test
+ public void secondaryAnimatedWithLabel() {
+ lh = new LinkHighlight(LINK_ID, SECONDARY_HIGHLIGHT)
+ .addMod(LinkHighlight.MOD_ANIMATED)
+ .setLabel(LABEL);
+
+ assertEquals("wrong flavor", SECONDARY_HIGHLIGHT, lh.flavor());
+ assertEquals("missing mod", 1, lh.mods().size());
+ Mod m = lh.mods().iterator().next();
+ assertEquals("wrong mod", LinkHighlight.MOD_ANIMATED, m);
+ assertEquals("wrong css", "secondary animated", lh.cssClasses());
+ assertEquals("wrong label", LABEL, lh.label());
+ }
+
+ @Test
+ public void customMod() {
+ lh = new LinkHighlight(LINK_ID, PRIMARY_HIGHLIGHT)
+ .addMod(new Mod(CUSTOM));
+
+ assertEquals("missing mod", 1, lh.mods().size());
+ Mod m = lh.mods().iterator().next();
+ assertEquals("wrong mod", CUSTOM, m.toString());
+ assertEquals("wrong css", "primary custom", lh.cssClasses());
+ }
+
+ @Test
+ public void severalMods() {
+ lh = new LinkHighlight(LINK_ID, SECONDARY_HIGHLIGHT)
+ .addMod(LinkHighlight.MOD_OPTICAL)
+ .addMod(LinkHighlight.MOD_ANIMATED)
+ .addMod(new Mod(CUSTOM));
+
+ assertEquals("missing mods", 3, lh.mods().size());
+ Iterator<Mod> iter = lh.mods().iterator();
+ // NOTE: we know we are using TreeSet as backing => sorted order
+ assertEquals("wrong mod", ANIMATED, iter.next().toString());
+ assertEquals("wrong mod", CUSTOM, iter.next().toString());
+ assertEquals("wrong mod", OPTICAL, iter.next().toString());
+ assertEquals("wrong css", "secondary animated custom optical", lh.cssClasses());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void noFlavor() {
+ new LinkHighlight(LINK_ID, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void noIdentity() {
+ new LinkHighlight(null, PRIMARY_HIGHLIGHT);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ModTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ModTest.java
new file mode 100644
index 00000000..bb40279c
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/ModTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ui.topo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link Mod}.
+ */
+public class ModTest {
+
+ private static final String AAA = "aaa";
+ private static final String BBB = "bbb";
+
+ private Mod mod1;
+ private Mod mod2;
+
+ @Test(expected = NullPointerException.class)
+ public void nullId() {
+ new Mod(null);
+ }
+
+ @Test
+ public void basic() {
+ mod1 = new Mod(AAA);
+ assertEquals("wrong id", AAA, mod1.toString());
+ }
+
+ @Test
+ public void equivalence() {
+ mod1 = new Mod(AAA);
+ mod2 = new Mod(AAA);
+ assertNotSame("oops", mod1, mod2);
+ assertEquals("not equivalent", mod1, mod2);
+ }
+
+ @Test
+ public void comparable() {
+ mod1 = new Mod(AAA);
+ mod2 = new Mod(BBB);
+ assertNotEquals("what?", mod1, mod2);
+ assertTrue(mod1.compareTo(mod2) < 0);
+ assertTrue(mod2.compareTo(mod1) > 0);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/NodeSelectionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/NodeSelectionTest.java
new file mode 100644
index 00000000..60cada45
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/NodeSelectionTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.ui.topo;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.Annotations;
+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.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link NodeSelection}.
+ */
+public class NodeSelectionTest {
+
+ private static class FakeDevice implements Device {
+
+ private final DeviceId id;
+
+ FakeDevice(DeviceId id) {
+ this.id = id;
+ }
+
+ @Override
+ public DeviceId id() {
+ return id;
+ }
+
+ @Override
+ public Type type() {
+ return null;
+ }
+
+ @Override
+ public String manufacturer() {
+ return null;
+ }
+
+ @Override
+ public String hwVersion() {
+ return null;
+ }
+
+ @Override
+ public String swVersion() {
+ return null;
+ }
+
+ @Override
+ public String serialNumber() {
+ return null;
+ }
+
+ @Override
+ public ChassisId chassisId() {
+ return null;
+ }
+
+ @Override
+ public Annotations annotations() {
+ return null;
+ }
+
+ @Override
+ public ProviderId providerId() {
+ return null;
+ }
+ }
+
+ private static class FakeHost implements Host {
+
+ private final HostId id;
+
+ FakeHost(HostId id) {
+ this.id = id;
+ }
+
+ @Override
+ public HostId id() {
+ return id;
+ }
+
+ @Override
+ public MacAddress mac() {
+ return null;
+ }
+
+ @Override
+ public VlanId vlan() {
+ return null;
+ }
+
+ @Override
+ public Set<IpAddress> ipAddresses() {
+ return null;
+ }
+
+ @Override
+ public HostLocation location() {
+ return null;
+ }
+
+ @Override
+ public Annotations annotations() {
+ return null;
+ }
+
+ @Override
+ public ProviderId providerId() {
+ return null;
+ }
+ }
+
+
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private static final String IDS = "ids";
+ private static final String HOVER = "hover";
+
+ private static final DeviceId DEVICE_1_ID = DeviceId.deviceId("Device-1");
+ private static final DeviceId DEVICE_2_ID = DeviceId.deviceId("Device-2");
+ private static final HostId HOST_A_ID = HostId.hostId("aa:aa:aa:aa:aa:aa/1");
+ private static final HostId HOST_B_ID = HostId.hostId("bb:bb:bb:bb:bb:bb/2");
+
+ private static final Device DEVICE_1 = new FakeDevice(DEVICE_1_ID);
+ private static final Device DEVICE_2 = new FakeDevice(DEVICE_2_ID);
+ private static final Host HOST_A = new FakeHost(HOST_A_ID);
+ private static final Host HOST_B = new FakeHost(HOST_B_ID);
+
+ // ==================
+ // == FAKE SERVICES
+ private static class FakeDevices extends DeviceServiceAdapter {
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ if (DEVICE_1_ID.equals(deviceId)) {
+ return DEVICE_1;
+ }
+ if (DEVICE_2_ID.equals(deviceId)) {
+ return DEVICE_2;
+ }
+ return null;
+ }
+ }
+
+ private static class FakeHosts extends HostServiceAdapter {
+ @Override
+ public Host getHost(HostId hostId) {
+ if (HOST_A_ID.equals(hostId)) {
+ return HOST_A;
+ }
+ if (HOST_B_ID.equals(hostId)) {
+ return HOST_B;
+ }
+ return null;
+ }
+ }
+
+ private DeviceService deviceService = new FakeDevices();
+ private HostService hostService = new FakeHosts();
+
+ private NodeSelection ns;
+
+ private ObjectNode objectNode() {
+ return mapper.createObjectNode();
+ }
+
+ private ArrayNode arrayNode() {
+ return mapper.createArrayNode();
+ }
+
+ private NodeSelection createNodeSelection(ObjectNode payload) {
+ return new NodeSelection(payload, deviceService, hostService);
+ }
+
+ // selection JSON payload creation methods
+ private ObjectNode emptySelection() {
+ ObjectNode payload = objectNode();
+ ArrayNode ids = arrayNode();
+ payload.set(IDS, ids);
+ return payload;
+ }
+
+ private ObjectNode oneDeviceSelected() {
+ ObjectNode payload = objectNode();
+ ArrayNode ids = arrayNode();
+ payload.set(IDS, ids);
+ ids.add(DEVICE_1_ID.toString());
+ return payload;
+ }
+
+ private ObjectNode oneHostSelected() {
+ ObjectNode payload = objectNode();
+ ArrayNode ids = arrayNode();
+ payload.set(IDS, ids);
+ ids.add(HOST_A_ID.toString());
+ return payload;
+ }
+
+ private ObjectNode twoHostsOneDeviceSelected() {
+ ObjectNode payload = objectNode();
+ ArrayNode ids = arrayNode();
+ payload.set(IDS, ids);
+ ids.add(HOST_A_ID.toString());
+ ids.add(DEVICE_1_ID.toString());
+ ids.add(HOST_B_ID.toString());
+ return payload;
+ }
+
+ private ObjectNode oneHostAndHoveringDeviceSelected() {
+ ObjectNode payload = objectNode();
+ ArrayNode ids = arrayNode();
+ payload.set(IDS, ids);
+ ids.add(HOST_A_ID.toString());
+ payload.put(HOVER, DEVICE_2_ID.toString());
+ return payload;
+ }
+
+ private ObjectNode twoDevicesOneHostAndHoveringHostSelected() {
+ ObjectNode payload = objectNode();
+ ArrayNode ids = arrayNode();
+ payload.set(IDS, ids);
+ ids.add(HOST_A_ID.toString());
+ ids.add(DEVICE_1_ID.toString());
+ ids.add(DEVICE_2_ID.toString());
+ payload.put(HOVER, HOST_B_ID.toString());
+ return payload;
+ }
+
+
+ @Test
+ public void basic() {
+ ns = createNodeSelection(emptySelection());
+ assertEquals("unexpected devices", 0, ns.devices().size());
+ assertEquals("unexpected devices w/hover", 0, ns.devicesWithHover().size());
+ assertEquals("unexpected hosts", 0, ns.hosts().size());
+ assertEquals("unexpected hosts w/hover", 0, ns.hostsWithHover().size());
+ assertTrue("unexpected selection", ns.none());
+ assertNull("hover?", ns.hovered());
+ }
+
+ @Test
+ public void oneDevice() {
+ ns = createNodeSelection(oneDeviceSelected());
+ assertEquals("missing device", 1, ns.devices().size());
+ assertTrue("missing device 1", ns.devices().contains(DEVICE_1));
+ assertEquals("missing device w/hover", 1, ns.devicesWithHover().size());
+ assertTrue("missing device 1 w/hover", ns.devicesWithHover().contains(DEVICE_1));
+ assertEquals("unexpected hosts", 0, ns.hosts().size());
+ assertEquals("unexpected hosts w/hover", 0, ns.hostsWithHover().size());
+ assertFalse("unexpected selection", ns.none());
+ assertNull("hover?", ns.hovered());
+ }
+
+ @Test
+ public void oneHost() {
+ ns = createNodeSelection(oneHostSelected());
+ assertEquals("unexpected devices", 0, ns.devices().size());
+ assertEquals("unexpected devices w/hover", 0, ns.devicesWithHover().size());
+ assertEquals("missing host", 1, ns.hosts().size());
+ assertTrue("missing host A", ns.hosts().contains(HOST_A));
+ assertEquals("missing host w/hover", 1, ns.hostsWithHover().size());
+ assertTrue("missing host A w/hover", ns.hostsWithHover().contains(HOST_A));
+ assertFalse("unexpected selection", ns.none());
+ assertNull("hover?", ns.hovered());
+ }
+
+ @Test
+ public void twoHostsOneDevice() {
+ ns = createNodeSelection(twoHostsOneDeviceSelected());
+ assertEquals("missing device", 1, ns.devices().size());
+ assertTrue("missing device 1", ns.devices().contains(DEVICE_1));
+ assertEquals("missing device w/hover", 1, ns.devicesWithHover().size());
+ assertTrue("missing device 1 w/hover", ns.devicesWithHover().contains(DEVICE_1));
+ assertEquals("unexpected hosts", 2, ns.hosts().size());
+ assertTrue("missing host A", ns.hosts().contains(HOST_A));
+ assertTrue("missing host B", ns.hosts().contains(HOST_B));
+ assertEquals("unexpected hosts w/hover", 2, ns.hostsWithHover().size());
+ assertTrue("missing host A w/hover", ns.hostsWithHover().contains(HOST_A));
+ assertTrue("missing host B w/hover", ns.hostsWithHover().contains(HOST_B));
+ assertFalse("unexpected selection", ns.none());
+ assertNull("hover?", ns.hovered());
+ }
+
+ @Test
+ public void oneHostAndHoveringDevice() {
+ ns = createNodeSelection(oneHostAndHoveringDeviceSelected());
+ assertEquals("unexpected devices", 0, ns.devices().size());
+ assertEquals("unexpected devices w/hover", 1, ns.devicesWithHover().size());
+ assertTrue("missing device 2 w/hover", ns.devicesWithHover().contains(DEVICE_2));
+ assertEquals("missing host", 1, ns.hosts().size());
+ assertTrue("missing host A", ns.hosts().contains(HOST_A));
+ assertEquals("missing host w/hover", 1, ns.hostsWithHover().size());
+ assertTrue("missing host A w/hover", ns.hostsWithHover().contains(HOST_A));
+ assertFalse("unexpected selection", ns.none());
+ assertEquals("missing hover device 2", DEVICE_2, ns.hovered());
+ }
+
+ @Test
+ public void twoDevicesOneHostAndHoveringHost() {
+ ns = createNodeSelection(twoDevicesOneHostAndHoveringHostSelected());
+ assertEquals("missing devices", 2, ns.devices().size());
+ assertTrue("missing device 1", ns.devices().contains(DEVICE_1));
+ assertTrue("missing device 2", ns.devices().contains(DEVICE_2));
+ assertEquals("missing devices w/hover", 2, ns.devicesWithHover().size());
+ assertTrue("missing device 1 w/hover", ns.devicesWithHover().contains(DEVICE_1));
+ assertTrue("missing device 2 w/hover", ns.devicesWithHover().contains(DEVICE_2));
+ assertEquals("missing host", 1, ns.hosts().size());
+ assertTrue("missing host A", ns.hosts().contains(HOST_A));
+ assertEquals("missing host w/hover", 2, ns.hostsWithHover().size());
+ assertTrue("missing host A w/hover", ns.hostsWithHover().contains(HOST_A));
+ assertTrue("missing host B w/hover", ns.hostsWithHover().contains(HOST_B));
+ assertFalse("unexpected selection", ns.none());
+ assertEquals("missing hover host B", HOST_B, ns.hovered());
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
new file mode 100644
index 00000000..b08ee4dc
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.ui.topo;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onosproject.ui.topo.PropertyPanel.Prop;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link PropertyPanel}.
+ */
+public class PropertyPanelTest {
+
+ private static final String TITLE_ORIG = "Original Title";
+ private static final String TYPE_ORIG = "Original type ID";
+ private static final String TITLE_NEW = "New Title";
+ private static final String TYPE_NEW = "New type";
+ private static final String SOME_IDENTIFICATION = "It's Me!";
+
+ private static final String KEY_A = "A";
+ private static final String KEY_B = "B";
+ private static final String KEY_C = "C";
+ private static final String SEP = "-";
+ private static final String KEY_Z = "Z";
+ private static final String VALUE_A = "Hay";
+ private static final String VALUE_B = "Bee";
+ private static final String VALUE_C = "Sea";
+ private static final String VALUE_Z = "Zed";
+
+ private static final Map<String, Prop> PROP_MAP = new HashMap<>();
+
+ private static class FooClass {
+ private final String s;
+ FooClass(String s) {
+ this.s = s;
+ }
+
+ @Override
+ public String toString() {
+ return ">" + s + "<";
+ }
+ }
+
+ private PropertyPanel pp;
+
+
+
+ @BeforeClass
+ public static void setUpClass() {
+ PROP_MAP.put(KEY_A, new Prop(KEY_A, VALUE_A));
+ PROP_MAP.put(KEY_B, new Prop(KEY_B, VALUE_B));
+ PROP_MAP.put(KEY_C, new Prop(KEY_C, VALUE_C));
+ PROP_MAP.put(KEY_Z, new Prop(KEY_Z, VALUE_Z));
+ PROP_MAP.put(SEP, new PropertyPanel.Separator());
+ }
+
+ @Test
+ public void basic() {
+ pp = new PropertyPanel(TITLE_ORIG, TYPE_ORIG);
+ assertEquals("wrong title", TITLE_ORIG, pp.title());
+ assertEquals("wrong type", TYPE_ORIG, pp.typeId());
+ assertNull("id?", pp.id());
+ assertEquals("unexpected props", 0, pp.properties().size());
+ assertEquals("unexpected buttons", 0, pp.buttons().size());
+ }
+
+ @Test
+ public void changeTitle() {
+ basic();
+ pp.title(TITLE_NEW);
+ assertEquals("wrong title", TITLE_NEW, pp.title());
+ }
+
+ @Test
+ public void changeType() {
+ basic();
+ pp.typeId(TYPE_NEW);
+ assertEquals("wrong type", TYPE_NEW, pp.typeId());
+ }
+
+ @Test
+ public void setId() {
+ basic();
+ pp.id(SOME_IDENTIFICATION);
+ assertEquals("wrong id", SOME_IDENTIFICATION, pp.id());
+ }
+
+ private void validateProps(String... keys) {
+ Iterator<Prop> iter = pp.properties().iterator();
+ for (String k: keys) {
+ Prop exp = PROP_MAP.get(k);
+ Prop act = iter.next();
+ assertEquals("Bad prop sequence", exp, act);
+ }
+ }
+
+ private void validateProp(String key, String expValue) {
+ Iterator<Prop> iter = pp.properties().iterator();
+ Prop prop = null;
+ while (iter.hasNext()) {
+ Prop p = iter.next();
+ if (p.key().equals(key)) {
+ prop = p;
+ break;
+ }
+ }
+ if (prop == null) {
+ fail("no prop found with key: " + key);
+ }
+ assertEquals("Wrong prop value", expValue, prop.value());
+ }
+
+ @Test
+ public void props() {
+ basic();
+ pp.addProp(KEY_A, VALUE_A)
+ .addProp(KEY_B, VALUE_B)
+ .addProp(KEY_C, VALUE_C);
+ assertEquals("bad props", 3, pp.properties().size());
+ validateProps(KEY_A, KEY_B, KEY_C);
+ }
+
+ @Test
+ public void separator() {
+ props();
+ pp.addSeparator()
+ .addProp(KEY_Z, VALUE_Z);
+
+ assertEquals("bad props", 5, pp.properties().size());
+ validateProps(KEY_A, KEY_B, KEY_C, SEP, KEY_Z);
+ }
+
+ @Test
+ public void removeAllProps() {
+ props();
+ assertEquals("wrong props", 3, pp.properties().size());
+ pp.removeAllProps();
+ assertEquals("unexpected props", 0, pp.properties().size());
+ }
+
+ @Test
+ public void adjustProps() {
+ props();
+ pp.removeProps(KEY_B, KEY_A);
+ pp.addProp(KEY_Z, VALUE_Z);
+ validateProps(KEY_C, KEY_Z);
+ }
+
+ @Test
+ public void intValues() {
+ basic();
+ pp.addProp(KEY_A, 200)
+ .addProp(KEY_B, 2000)
+ .addProp(KEY_C, 1234567);
+
+ validateProp(KEY_A, "200");
+ validateProp(KEY_B, "2,000");
+ validateProp(KEY_C, "1,234,567");
+ }
+
+ @Test
+ public void longValues() {
+ basic();
+ pp.addProp(KEY_A, 200L)
+ .addProp(KEY_B, 2000L)
+ .addProp(KEY_C, 1234567L)
+ .addProp(KEY_Z, Long.MAX_VALUE);
+
+ validateProp(KEY_A, "200");
+ validateProp(KEY_B, "2,000");
+ validateProp(KEY_C, "1,234,567");
+ validateProp(KEY_Z, "9,223,372,036,854,775,807");
+ }
+
+ @Test
+ public void objectValue() {
+ basic();
+ pp.addProp(KEY_A, new FooClass("a"))
+ .addProp(KEY_B, new FooClass("bxyyzy"), "[xz]");
+
+ validateProp(KEY_A, ">a<");
+ validateProp(KEY_B, ">byyy<");
+ }
+
+ private static final ButtonId BD_A = new ButtonId(KEY_A);
+ private static final ButtonId BD_B = new ButtonId(KEY_B);
+ private static final ButtonId BD_C = new ButtonId(KEY_C);
+ private static final ButtonId BD_Z = new ButtonId(KEY_Z);
+
+ private void verifyButtons(String... keys) {
+ Iterator<ButtonId> iter = pp.buttons().iterator();
+ for (String k: keys) {
+ assertEquals("wrong button", k, iter.next().id());
+ }
+ assertFalse("too many buttons", iter.hasNext());
+ }
+
+ @Test
+ public void buttons() {
+ basic();
+ pp.addButton(BD_A)
+ .addButton(BD_B);
+ assertEquals("wrong buttons", 2, pp.buttons().size());
+ verifyButtons(KEY_A, KEY_B);
+
+ pp.removeButtons(BD_B)
+ .addButton(BD_C)
+ .addButton(BD_Z);
+ assertEquals("wrong buttons", 3, pp.buttons().size());
+ verifyButtons(KEY_A, KEY_C, KEY_Z);
+
+ pp.removeAllButtons()
+ .addButton(BD_B);
+ assertEquals("wrong buttons", 1, pp.buttons().size());
+ verifyButtons(KEY_B);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java
new file mode 100644
index 00000000..6a3bfa43
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.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.ui.topo;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Test;
+import org.onosproject.ui.JsonUtils;
+import org.onosproject.ui.topo.Highlights.Amount;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link TopoJson}.
+ */
+public class TopoJsonTest {
+
+ private ObjectNode payload;
+
+ private void checkArrayLength(String key, int expLen) {
+ ArrayNode a = (ArrayNode) payload.get(key);
+ assertEquals("wrong size: " + key, expLen, a.size());
+ }
+
+ private void checkEmptyArrays() {
+ checkArrayLength(TopoJson.DEVICES, 0);
+ checkArrayLength(TopoJson.HOSTS, 0);
+ checkArrayLength(TopoJson.LINKS, 0);
+ }
+
+ @Test
+ public void basicHighlights() {
+ Highlights h = new Highlights();
+ payload = TopoJson.json(h);
+ checkEmptyArrays();
+ String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
+ assertEquals("subdue", "", subdue);
+ }
+
+ @Test
+ public void subdueMinimalHighlights() {
+ Highlights h = new Highlights().subdueAllElse(Amount.MINIMALLY);
+ payload = TopoJson.json(h);
+ checkEmptyArrays();
+ String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
+ assertEquals("not min", "min", subdue);
+ }
+
+ @Test
+ public void subdueMaximalHighlights() {
+ Highlights h = new Highlights().subdueAllElse(Amount.MAXIMALLY);
+ payload = TopoJson.json(h);
+ checkEmptyArrays();
+ String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
+ assertEquals("not max", "max", subdue);
+ }
+}
diff --git a/framework/src/onos/core/api/src/test/resources/css.html b/framework/src/onos/core/api/src/test/resources/css.html
new file mode 100644
index 00000000..3079957b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/css.html
@@ -0,0 +1,2 @@
+foo-css
+bar-css \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/resources/custom/css.html b/framework/src/onos/core/api/src/test/resources/custom/css.html
new file mode 100644
index 00000000..e67f4a2b
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/custom/css.html
@@ -0,0 +1 @@
+custom-css
diff --git a/framework/src/onos/core/api/src/test/resources/custom/js.html b/framework/src/onos/core/api/src/test/resources/custom/js.html
new file mode 100644
index 00000000..f87844b1
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/custom/js.html
@@ -0,0 +1 @@
+custom-js \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/resources/js.html b/framework/src/onos/core/api/src/test/resources/js.html
new file mode 100644
index 00000000..50b63b34
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/js.html
@@ -0,0 +1,2 @@
+foo-js
+bar-js \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.1.xml b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.1.xml
new file mode 100644
index 00000000..e4490450
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.1.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<drivers>
+ <driver name="foo.0" manufacturer="Circus" hwVersion="1.2" swVersion="2.0">
+ <behaviour api="org.onosproject.net.driver.TestBehaviour"
+ impl="org.onosproject.net.driver.TestBehaviourImpl"/>
+ </driver>
+
+ <driver name="foo.1" extends="foo.0" manufacturer="Circus" hwVersion="1.2a" swVersion="2.2">
+ <fingerprint>ding</fingerprint>
+ <fingerprint>bat</fingerprint>
+
+ <behaviour api="org.onosproject.net.driver.TestBehaviourTwo"
+ impl="org.onosproject.net.driver.TestBehaviourTwoImpl"/>
+
+ <property name="p1">v1</property>
+ <property name="p2">v2</property>
+ </driver>
+</drivers> \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.bad.xml b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.bad.xml
new file mode 100644
index 00000000..5815ba38
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.bad.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<drivers>
+ <driver name="foo.1" manufacturer="Circus" hwVersion="1.2a" swVersion="2.2">
+ <behaviour api="org.onosproject.net.driver.TestBehaviour"
+ impl="org.onosproject.net.driver.TestBehaviourImpl"/>
+ </driverXXXXX> <!-- intentional to test handling malformed XML -->
+</drivers> \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noclass.xml b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noclass.xml
new file mode 100644
index 00000000..e46b42e3
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noclass.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<drivers>
+ <driver name="foo.1" manufacturer="Circus" hwVersion="1.2a" swVersion="2.2">
+ <behaviour api="org.onosproject.net.driver.TestBehaviourXX"
+ impl="org.onosproject.net.driver.TestBehaviourImpl"/>
+ </driver>
+</drivers> \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noconstructor.xml b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noconstructor.xml
new file mode 100644
index 00000000..873656d8
--- /dev/null
+++ b/framework/src/onos/core/api/src/test/resources/org/onosproject/net/driver/drivers.noconstructor.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<drivers>
+ <driver name="foo.1" manufacturer="Circus" hwVersion="1.2a" swVersion="2.2">
+ <behaviour api="org.onosproject.net.driver.TestBehaviour"
+ impl="org.onosproject.net.driver.TestBehaviourNoConstructorImpl"/>
+ </driver>
+</drivers> \ No newline at end of file
diff --git a/framework/src/onos/core/common/pom.xml b/framework/src/onos/core/common/pom.xml
new file mode 100644
index 00000000..71c0fe40
--- /dev/null
+++ b/framework/src/onos/core/common/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core-common</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS utilities common to the core modules</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java
new file mode 100644
index 00000000..8c714625
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java
@@ -0,0 +1,64 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Annotated;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+
+/**
+ * Base JSON codec for annotated entities.
+ */
+public abstract class AnnotatedCodec<T extends Annotated> extends JsonCodec<T> {
+
+ /**
+ * Adds JSON encoding of the given item annotations to the specified node.
+ *
+ * @param node node to add annotations to
+ * @param entity annotated entity
+ * @param context encode context
+ * @return the given node
+ */
+ protected ObjectNode annotate(ObjectNode node, T entity, CodecContext context) {
+ if (!entity.annotations().keys().isEmpty()) {
+ JsonCodec<Annotations> codec = context.codec(Annotations.class);
+ node.set("annotations", codec.encode(entity.annotations(), context));
+ }
+ return node;
+ }
+
+ /**
+ * Extracts annotations of given Object.
+ *
+ * @param objNode annotated JSON object node
+ * @param context decode context
+ * @return extracted Annotations
+ */
+ protected Annotations extractAnnotations(ObjectNode objNode, CodecContext context) {
+
+ JsonCodec<Annotations> codec = context.codec(Annotations.class);
+ if (objNode.has("annotations") && objNode.isObject()) {
+ return codec.decode(get(objNode, "annotations"), context);
+ } else {
+ return DefaultAnnotations.EMPTY;
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java
new file mode 100644
index 00000000..de6ca1b9
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java
@@ -0,0 +1,49 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultAnnotations.Builder;
+
+/**
+ * Annotations JSON codec.
+ */
+public final class AnnotationsCodec extends JsonCodec<Annotations> {
+
+ @Override
+ public ObjectNode encode(Annotations annotations, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ for (String key : annotations.keys()) {
+ result.put(key, annotations.value(key));
+ }
+ return result;
+ }
+
+ @Override
+ public Annotations decode(ObjectNode json, CodecContext context) {
+ Builder builder = DefaultAnnotations.builder();
+
+ json.fields().forEachRemaining(e ->
+ builder.set(e.getKey(), e.getValue().asText()));
+
+ return builder.build();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
new file mode 100644
index 00000000..b2cab094
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.Application;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Application JSON codec.
+ */
+public final class ApplicationCodec extends JsonCodec<Application> {
+
+ @Override
+ public ObjectNode encode(Application app, CodecContext context) {
+ checkNotNull(app, "Application cannot be null");
+ ApplicationService service = context.getService(ApplicationService.class);
+ ObjectNode result = context.mapper().createObjectNode()
+ .put("name", app.id().name())
+ .put("id", app.id().id())
+ .put("version", app.version().toString())
+ .put("description", app.description())
+ .put("origin", app.origin())
+ .put("permissions", app.permissions().toString())
+ .put("featuresRepo", app.featuresRepo().isPresent() ?
+ app.featuresRepo().get().toString() : "")
+ .put("features", app.features().toString())
+ .put("state", service.getState(app.id()).toString());
+ return result;
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
new file mode 100644
index 00000000..eb53152e
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -0,0 +1,132 @@
+/*
+ * 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.codec.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.Service;
+import org.onlab.packet.Ethernet;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.Application;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.Port;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.flow.FlowEntry;
+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.instructions.Instruction;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.statistic.Load;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implementation of the JSON codec brokering service.
+ */
+@Component(immediate = true)
+@Service
+public class CodecManager implements CodecService {
+
+ private static Logger log = LoggerFactory.getLogger(CodecManager.class);
+
+ private final Map<Class<?>, JsonCodec> codecs = new ConcurrentHashMap<>();
+
+ @Activate
+ public void activate() {
+ codecs.clear();
+ registerCodec(Application.class, new ApplicationCodec());
+ registerCodec(ControllerNode.class, new ControllerNodeCodec());
+ registerCodec(Annotations.class, new AnnotationsCodec());
+ registerCodec(Device.class, new DeviceCodec());
+ registerCodec(Port.class, new PortCodec());
+ registerCodec(ConnectPoint.class, new ConnectPointCodec());
+ registerCodec(Link.class, new LinkCodec());
+ registerCodec(Host.class, new HostCodec());
+ registerCodec(HostLocation.class, new HostLocationCodec());
+ registerCodec(HostToHostIntent.class, new HostToHostIntentCodec());
+ registerCodec(PointToPointIntent.class, new PointToPointIntentCodec());
+ registerCodec(Intent.class, new IntentCodec());
+ registerCodec(ConnectivityIntent.class, new ConnectivityIntentCodec());
+ registerCodec(FlowEntry.class, new FlowEntryCodec());
+ registerCodec(FlowRule.class, new FlowRuleCodec());
+ registerCodec(TrafficTreatment.class, new TrafficTreatmentCodec());
+ registerCodec(TrafficSelector.class, new TrafficSelectorCodec());
+ registerCodec(Instruction.class, new InstructionCodec());
+ registerCodec(Criterion.class, new CriterionCodec());
+ registerCodec(Ethernet.class, new EthernetCodec());
+ registerCodec(Constraint.class, new ConstraintCodec());
+ registerCodec(Topology.class, new TopologyCodec());
+ registerCodec(TopologyCluster.class, new TopologyClusterCodec());
+ registerCodec(Path.class, new PathCodec());
+ registerCodec(Group.class, new GroupCodec());
+ registerCodec(Driver.class, new DriverCodec());
+ registerCodec(GroupBucket.class, new GroupBucketCodec());
+ registerCodec(Load.class, new LoadCodec());
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ codecs.clear();
+ log.info("Stopped");
+ }
+
+ @Override
+ public Set<Class<?>> getCodecs() {
+ return ImmutableSet.copyOf(codecs.keySet());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> JsonCodec<T> getCodec(Class<T> entityClass) {
+ return codecs.get(entityClass);
+ }
+
+ @Override
+ public <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec) {
+ codecs.putIfAbsent(entityClass, codec);
+ }
+
+ @Override
+ public void unregisterCodec(Class<?> entityClass) {
+ codecs.remove(entityClass);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java
new file mode 100644
index 00000000..ac23ef89
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java
@@ -0,0 +1,74 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.PortNumber;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Connection point JSON codec.
+ */
+public final class ConnectPointCodec extends JsonCodec<ConnectPoint> {
+
+ // JSON field names
+ private static final String ELEMENT_HOST = "host";
+ private static final String ELEMENT_DEVICE = "device";
+ private static final String PORT = "port";
+
+ @Override
+ public ObjectNode encode(ConnectPoint point, CodecContext context) {
+ checkNotNull(point, "Connect point cannot be null");
+ ObjectNode root = context.mapper().createObjectNode()
+ .put(PORT, point.port().toString());
+
+ if (point.elementId() instanceof DeviceId) {
+ root.put(ELEMENT_DEVICE, point.deviceId().toString());
+ } else if (point.elementId() instanceof HostId) {
+ root.put(ELEMENT_HOST, point.hostId().toString());
+ }
+
+ return root;
+ }
+
+ @Override
+ public ConnectPoint decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ ElementId elementId;
+ if (json.has(ELEMENT_DEVICE)) {
+ elementId = DeviceId.deviceId(json.get(ELEMENT_DEVICE).asText());
+ } else if (json.has(ELEMENT_HOST)) {
+ elementId = HostId.hostId(json.get(ELEMENT_HOST).asText());
+ } else {
+ // invalid JSON
+ return null;
+ }
+ PortNumber portNumber = portNumber(json.get(PORT).asText());
+ return new ConnectPoint(elementId, portNumber);
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java
new file mode 100644
index 00000000..9e8cd86c
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java
@@ -0,0 +1,118 @@
+/*
+ * 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.codec.impl;
+
+import java.util.ArrayList;
+import java.util.stream.IntStream;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Connectivity intent codec.
+ */
+public final class ConnectivityIntentCodec extends JsonCodec<ConnectivityIntent> {
+
+ private static final String CONSTRAINTS = "constraints";
+ private static final String SELECTOR = "selector";
+ private static final String TREATMENT = "treatment";
+
+ @Override
+ public ObjectNode encode(ConnectivityIntent intent, CodecContext context) {
+ checkNotNull(intent, "Connectivity intent cannot be null");
+
+ final JsonCodec<Intent> intentCodec = context.codec(Intent.class);
+ final ObjectNode result = intentCodec.encode(intent, context);
+
+ if (intent.selector() != null) {
+ final JsonCodec<TrafficSelector> selectorCodec =
+ context.codec(TrafficSelector.class);
+ result.set(SELECTOR, selectorCodec.encode(intent.selector(), context));
+ }
+
+ if (intent.treatment() != null) {
+ final JsonCodec<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ result.set(TREATMENT, treatmentCodec.encode(intent.treatment(), context));
+ }
+
+ result.put(IntentCodec.PRIORITY, intent.priority());
+
+ if (intent.constraints() != null) {
+ final ArrayNode jsonConstraints = result.putArray(CONSTRAINTS);
+
+ if (intent.constraints() != null) {
+ final JsonCodec<Constraint> constraintCodec =
+ context.codec(Constraint.class);
+ for (final Constraint constraint : intent.constraints()) {
+ final ObjectNode constraintNode =
+ constraintCodec.encode(constraint, context);
+ jsonConstraints.add(constraintNode);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Extracts connectivity intent specific attributes from a JSON object
+ * and adds them to a builder.
+ *
+ * @param json root JSON object
+ * @param context code context
+ * @param builder builder to use for storing the attributes. Constraints,
+ * selector and treatment are modified by this call.
+ */
+ public static void intentAttributes(ObjectNode json, CodecContext context,
+ ConnectivityIntent.Builder builder) {
+ JsonNode constraintsJson = json.get(CONSTRAINTS);
+ if (constraintsJson != null) {
+ JsonCodec<Constraint> constraintsCodec = context.codec(Constraint.class);
+ ArrayList<Constraint> constraints = new ArrayList<>(constraintsJson.size());
+ IntStream.range(0, constraintsJson.size())
+ .forEach(i -> constraints.add(
+ constraintsCodec.decode(get(constraintsJson, i),
+ context)));
+ builder.constraints(constraints);
+ }
+
+ ObjectNode selectorJson = get(json, SELECTOR);
+ if (selectorJson != null) {
+ JsonCodec<TrafficSelector> selectorCodec = context.codec(TrafficSelector.class);
+ TrafficSelector selector = selectorCodec.decode(selectorJson, context);
+ builder.selector(selector);
+ }
+
+ ObjectNode treatmentJson = get(json, TREATMENT);
+ if (treatmentJson != null) {
+ JsonCodec<TrafficTreatment> treatmentCodec = context.codec(TrafficTreatment.class);
+ TrafficTreatment treatment = treatmentCodec.decode(treatmentJson, context);
+ builder.treatment(treatment);
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java
new file mode 100644
index 00000000..50738341
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java
@@ -0,0 +1,63 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.intent.Constraint;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Constraint JSON codec.
+ */
+public final class ConstraintCodec extends JsonCodec<Constraint> {
+
+ protected static final String MISSING_MEMBER_MESSAGE =
+ " member is required in Constraint";
+ protected static final String TYPE = "type";
+ protected static final String TYPES = "types";
+ protected static final String INCLUSIVE = "inclusive";
+ protected static final String KEY = "key";
+ protected static final String THRESHOLD = "threshold";
+ protected static final String BANDWIDTH = "bandwidth";
+ protected static final String LAMBDA = "lambda";
+ protected static final String LATENCY_MILLIS = "latencyMillis";
+ protected static final String OBSTACLES = "obstacles";
+ protected static final String WAYPOINTS = "waypoints";
+
+ @Override
+ public ObjectNode encode(Constraint constraint, CodecContext context) {
+ checkNotNull(constraint, "Constraint cannot be null");
+
+ final EncodeConstraintCodecHelper encodeCodec =
+ new EncodeConstraintCodecHelper(constraint, context);
+
+ return encodeCodec.encode();
+ }
+
+ @Override
+ public Constraint decode(ObjectNode json, CodecContext context) {
+ checkNotNull(json, "JSON cannot be null");
+
+ final DecodeConstraintCodecHelper decodeCodec =
+ new DecodeConstraintCodecHelper(json);
+
+ return decodeCodec.decode();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
new file mode 100644
index 00000000..65d758ee
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
@@ -0,0 +1,57 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.cluster.DefaultControllerNode.DEFAULT_PORT;
+
+/**
+ * Device JSON codec.
+ */
+public final class ControllerNodeCodec extends JsonCodec<ControllerNode> {
+
+ @Override
+ public ObjectNode encode(ControllerNode node, CodecContext context) {
+ checkNotNull(node, "Controller node cannot be null");
+ ClusterService service = context.getService(ClusterService.class);
+ return context.mapper().createObjectNode()
+ .put("id", node.id().toString())
+ .put("ip", node.ip().toString())
+ .put("tcpPort", node.tcpPort())
+ .put("status", service.getState(node.id()).toString());
+ }
+
+
+ @Override
+ public ControllerNode decode(ObjectNode json, CodecContext context) {
+ checkNotNull(json, "JSON cannot be null");
+ String ip = json.path("ip").asText();
+ return new DefaultControllerNode(new NodeId(json.path("id").asText(ip)),
+ IpAddress.valueOf(ip),
+ json.path("tcpPort").asInt(DEFAULT_PORT));
+ }
+
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java
new file mode 100644
index 00000000..76f621f2
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java
@@ -0,0 +1,78 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Criterion codec.
+ */
+public final class CriterionCodec extends JsonCodec<Criterion> {
+
+ protected static final Logger log =
+ LoggerFactory.getLogger(CriterionCodec.class);
+
+ protected static final String TYPE = "type";
+ protected static final String ETH_TYPE = "ethType";
+ protected static final String MAC = "mac";
+ protected static final String PORT = "port";
+ protected static final String METADATA = "metadata";
+
+ protected static final String VLAN_ID = "vlanId";
+ protected static final String PRIORITY = "priority";
+ protected static final String IP_DSCP = "ipDscp";
+ protected static final String IP_ECN = "ipEcn";
+ protected static final String PROTOCOL = "protocol";
+ protected static final String IP = "ip";
+ protected static final String TCP_PORT = "tcpPort";
+ protected static final String UDP_PORT = "udpPort";
+ protected static final String SCTP_PORT = "sctpPort";
+ protected static final String ICMP_TYPE = "icmpType";
+ protected static final String ICMP_CODE = "icmpCode";
+ protected static final String FLOW_LABEL = "flowLabel";
+ protected static final String ICMPV6_TYPE = "icmpv6Type";
+ protected static final String ICMPV6_CODE = "icmpv6Code";
+ protected static final String TARGET_ADDRESS = "targetAddress";
+ protected static final String LABEL = "label";
+ protected static final String EXT_HDR_FLAGS = "exthdrFlags";
+ protected static final String LAMBDA = "lambda";
+ protected static final String GRID_TYPE = "gridType";
+ protected static final String CHANNEL_SPACING = "channelSpacing";
+ protected static final String SPACING_MULIPLIER = "spacingMultiplier";
+ protected static final String SLOT_GRANULARITY = "slotGranularity";
+ protected static final String OCH_SIGNAL_ID = "ochSignalId";
+ protected static final String TUNNEL_ID = "tunnelId";
+
+ @Override
+ public ObjectNode encode(Criterion criterion, CodecContext context) {
+ EncodeCriterionCodecHelper encoder = new EncodeCriterionCodecHelper(criterion, context);
+ return encoder.encode();
+ }
+
+ @Override
+ public Criterion decode(ObjectNode json, CodecContext context) {
+ DecodeCriterionCodecHelper decoder = new DecodeCriterionCodecHelper(json);
+ return decoder.decode();
+ }
+
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java
new file mode 100644
index 00000000..5746003c
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java
@@ -0,0 +1,225 @@
+/*
+ * 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.codec.impl;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.stream.IntStream;
+
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.AsymmetricPathConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.LinkTypeConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.LambdaResource;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Constraint JSON decoder.
+ */
+public final class DecodeConstraintCodecHelper {
+ private final ObjectNode json;
+
+ /**
+ * Constructs a constraint decoder.
+ *
+ * @param json object node to decode
+ */
+ public DecodeConstraintCodecHelper(ObjectNode json) {
+ this.json = json;
+ }
+
+ /**
+ * Decodes a link type constraint.
+ *
+ * @return link type constraint object.
+ */
+ private Constraint decodeLinkTypeConstraint() {
+ boolean inclusive = nullIsIllegal(json.get(ConstraintCodec.INCLUSIVE),
+ ConstraintCodec.INCLUSIVE + ConstraintCodec.MISSING_MEMBER_MESSAGE).asBoolean();
+
+ JsonNode types = nullIsIllegal(json.get(ConstraintCodec.TYPES),
+ ConstraintCodec.TYPES + ConstraintCodec.MISSING_MEMBER_MESSAGE);
+ if (types.size() < 1) {
+ throw new IllegalArgumentException(
+ "types array in link constraint must have at least one value");
+ }
+
+ ArrayList<Link.Type> typesEntries = new ArrayList<>(types.size());
+ IntStream.range(0, types.size())
+ .forEach(index ->
+ typesEntries.add(Link.Type.valueOf(types.get(index).asText())));
+
+ return new LinkTypeConstraint(inclusive,
+ typesEntries.toArray(new Link.Type[types.size()]));
+ }
+
+ /**
+ * Decodes an annotation constraint.
+ *
+ * @return annotation constraint object.
+ */
+ private Constraint decodeAnnotationConstraint() {
+ String key = nullIsIllegal(json.get(ConstraintCodec.KEY),
+ ConstraintCodec.KEY + ConstraintCodec.MISSING_MEMBER_MESSAGE)
+ .asText();
+ double threshold = nullIsIllegal(json.get(ConstraintCodec.THRESHOLD),
+ ConstraintCodec.THRESHOLD + ConstraintCodec.MISSING_MEMBER_MESSAGE)
+ .asDouble();
+
+ return new AnnotationConstraint(key, threshold);
+ }
+
+ /**
+ * Decodes a lambda constraint.
+ *
+ * @return lambda constraint object.
+ */
+ private Constraint decodeLambdaConstraint() {
+ long lambda = nullIsIllegal(json.get(ConstraintCodec.LAMBDA),
+ ConstraintCodec.LAMBDA + ConstraintCodec.MISSING_MEMBER_MESSAGE)
+ .asLong();
+
+ return new LambdaConstraint(LambdaResource.valueOf(new IndexedLambda(lambda)));
+ }
+
+ /**
+ * Decodes a latency constraint.
+ *
+ * @return latency constraint object.
+ */
+ private Constraint decodeLatencyConstraint() {
+ long latencyMillis = nullIsIllegal(json.get(ConstraintCodec.LATENCY_MILLIS),
+ ConstraintCodec.LATENCY_MILLIS + ConstraintCodec.MISSING_MEMBER_MESSAGE)
+ .asLong();
+
+ return new LatencyConstraint(Duration.ofMillis(latencyMillis));
+ }
+
+ /**
+ * Decodes an obstacle constraint.
+ *
+ * @return obstacle constraint object.
+ */
+ private Constraint decodeObstacleConstraint() {
+ JsonNode obstacles = nullIsIllegal(json.get(ConstraintCodec.OBSTACLES),
+ ConstraintCodec.OBSTACLES + ConstraintCodec.MISSING_MEMBER_MESSAGE);
+ if (obstacles.size() < 1) {
+ throw new IllegalArgumentException(
+ "obstacles array in obstacles constraint must have at least one value");
+ }
+
+ ArrayList<DeviceId> obstacleEntries = new ArrayList<>(obstacles.size());
+ IntStream.range(0, obstacles.size())
+ .forEach(index ->
+ obstacleEntries.add(DeviceId.deviceId(obstacles.get(index).asText())));
+
+ return new ObstacleConstraint(
+ obstacleEntries.toArray(new DeviceId[obstacles.size()]));
+ }
+
+ /**
+ * Decodes a waypoint constraint.
+ *
+ * @return waypoint constraint object.
+ */
+ private Constraint decodeWaypointConstraint() {
+ JsonNode waypoints = nullIsIllegal(json.get(ConstraintCodec.WAYPOINTS),
+ ConstraintCodec.WAYPOINTS + ConstraintCodec.MISSING_MEMBER_MESSAGE);
+ if (waypoints.size() < 1) {
+ throw new IllegalArgumentException(
+ "obstacles array in obstacles constraint must have at least one value");
+ }
+
+ ArrayList<DeviceId> waypointEntries = new ArrayList<>(waypoints.size());
+ IntStream.range(0, waypoints.size())
+ .forEach(index ->
+ waypointEntries.add(DeviceId.deviceId(waypoints.get(index).asText())));
+
+ return new WaypointConstraint(
+ waypointEntries.toArray(new DeviceId[waypoints.size()]));
+ }
+
+ /**
+ * Decodes an asymmetric path constraint.
+ *
+ * @return asymmetric path constraint object.
+ */
+ private Constraint decodeAsymmetricPathConstraint() {
+ return new AsymmetricPathConstraint();
+ }
+
+ /**
+ * Decodes a bandwidth constraint.
+ *
+ * @return bandwidth constraint object.
+ */
+ private Constraint decodeBandwidthConstraint() {
+ double bandwidth = nullIsIllegal(json.get(ConstraintCodec.BANDWIDTH),
+ ConstraintCodec.BANDWIDTH + ConstraintCodec.MISSING_MEMBER_MESSAGE)
+ .asDouble();
+
+ return new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(bandwidth)));
+ }
+
+ /**
+ * Decodes the given constraint.
+ *
+ * @return constraint object.
+ */
+ public Constraint decode() {
+ final String type = nullIsIllegal(json.get(ConstraintCodec.TYPE),
+ ConstraintCodec.TYPE + ConstraintCodec.MISSING_MEMBER_MESSAGE)
+ .asText();
+
+ if (type.equals(BandwidthConstraint.class.getSimpleName())) {
+ return decodeBandwidthConstraint();
+ } else if (type.equals(LambdaConstraint.class.getSimpleName())) {
+ return decodeLambdaConstraint();
+ } else if (type.equals(LinkTypeConstraint.class.getSimpleName())) {
+ return decodeLinkTypeConstraint();
+ } else if (type.equals(AnnotationConstraint.class.getSimpleName())) {
+ return decodeAnnotationConstraint();
+ } else if (type.equals(LatencyConstraint.class.getSimpleName())) {
+ return decodeLatencyConstraint();
+ } else if (type.equals(ObstacleConstraint.class.getSimpleName())) {
+ return decodeObstacleConstraint();
+ } else if (type.equals(WaypointConstraint.class.getSimpleName())) {
+ return decodeWaypointConstraint();
+ } else if (type.equals(AsymmetricPathConstraint.class.getSimpleName())) {
+ return decodeAsymmetricPathConstraint();
+ } else if (type.equals(LinkTypeConstraint.class.getSimpleName())) {
+ return decodeLinkTypeConstraint();
+ } else if (type.equals(AnnotationConstraint.class.getSimpleName())) {
+ return decodeAnnotationConstraint();
+ }
+ throw new IllegalArgumentException("Instruction type "
+ + type + " is not supported");
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java
new file mode 100644
index 00000000..4e0f2bd9
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java
@@ -0,0 +1,449 @@
+/*
+ * 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.codec.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Decode portion of the criterion codec.
+ */
+public final class DecodeCriterionCodecHelper {
+
+ private final ObjectNode json;
+
+ protected static final String MISSING_MEMBER_MESSAGE =
+ " member is required in Criterion";
+
+ private interface CriterionDecoder {
+ Criterion decodeCriterion(ObjectNode json);
+ }
+ private final Map<String, CriterionDecoder> decoderMap;
+
+ /**
+ * Creates a decode criterion codec object.
+ * Initializes the lookup map for criterion subclass decoders.
+ *
+ * @param json JSON object to decode
+ */
+ public DecodeCriterionCodecHelper(ObjectNode json) {
+ this.json = json;
+ decoderMap = new HashMap<>();
+
+ decoderMap.put(Criterion.Type.IN_PORT.name(), new InPortDecoder());
+ decoderMap.put(Criterion.Type.IN_PHY_PORT.name(), new InPhyPortDecoder());
+ decoderMap.put(Criterion.Type.METADATA.name(), new MetadataDecoder());
+ decoderMap.put(Criterion.Type.ETH_DST.name(), new EthDstDecoder());
+ decoderMap.put(Criterion.Type.ETH_SRC.name(), new EthSrcDecoder());
+ decoderMap.put(Criterion.Type.ETH_TYPE.name(), new EthTypeDecoder());
+ decoderMap.put(Criterion.Type.VLAN_VID.name(), new VlanVidDecoder());
+ decoderMap.put(Criterion.Type.VLAN_PCP.name(), new VlanPcpDecoder());
+ decoderMap.put(Criterion.Type.IP_DSCP.name(), new IpDscpDecoder());
+ decoderMap.put(Criterion.Type.IP_ECN.name(), new IpEcnDecoder());
+ decoderMap.put(Criterion.Type.IP_PROTO.name(), new IpProtoDecoder());
+ decoderMap.put(Criterion.Type.IPV4_SRC.name(), new IpV4SrcDecoder());
+ decoderMap.put(Criterion.Type.IPV4_DST.name(), new IpV4DstDecoder());
+ decoderMap.put(Criterion.Type.TCP_SRC.name(), new TcpSrcDecoder());
+ decoderMap.put(Criterion.Type.TCP_DST.name(), new TcpDstDecoder());
+ decoderMap.put(Criterion.Type.UDP_SRC.name(), new UdpSrcDecoder());
+ decoderMap.put(Criterion.Type.UDP_DST.name(), new UdpDstDecoder());
+ decoderMap.put(Criterion.Type.SCTP_SRC.name(), new SctpSrcDecoder());
+ decoderMap.put(Criterion.Type.SCTP_DST.name(), new SctpDstDecoder());
+ decoderMap.put(Criterion.Type.ICMPV4_TYPE.name(), new IcmpV4TypeDecoder());
+ decoderMap.put(Criterion.Type.ICMPV4_CODE.name(), new IcmpV4CodeDecoder());
+ decoderMap.put(Criterion.Type.IPV6_SRC.name(), new IpV6SrcDecoder());
+ decoderMap.put(Criterion.Type.IPV6_DST.name(), new IpV6DstDecoder());
+ decoderMap.put(Criterion.Type.IPV6_FLABEL.name(), new IpV6FLabelDecoder());
+ decoderMap.put(Criterion.Type.ICMPV6_TYPE.name(), new IcmpV6TypeDecoder());
+ decoderMap.put(Criterion.Type.ICMPV6_CODE.name(), new IcmpV6CodeDecoder());
+ decoderMap.put(Criterion.Type.IPV6_ND_TARGET.name(), new V6NDTargetDecoder());
+ decoderMap.put(Criterion.Type.IPV6_ND_SLL.name(), new V6NDSllDecoder());
+ decoderMap.put(Criterion.Type.IPV6_ND_TLL.name(), new V6NDTllDecoder());
+ decoderMap.put(Criterion.Type.MPLS_LABEL.name(), new MplsLabelDecoder());
+ decoderMap.put(Criterion.Type.IPV6_EXTHDR.name(), new IpV6ExthdrDecoder());
+ decoderMap.put(Criterion.Type.OCH_SIGID.name(), new OchSigIdDecoder());
+ decoderMap.put(Criterion.Type.OCH_SIGTYPE.name(), new OchSigTypeDecoder());
+ decoderMap.put(Criterion.Type.TUNNEL_ID.name(), new TunnelIdDecoder());
+ }
+
+ private class EthTypeDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ int ethType = nullIsIllegal(json.get(CriterionCodec.ETH_TYPE),
+ CriterionCodec.ETH_TYPE + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchEthType(ethType);
+ }
+ }
+
+ private class EthDstDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC),
+ CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText());
+
+ return Criteria.matchEthDst(mac);
+ }
+ }
+
+ private class EthSrcDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC),
+ CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText());
+
+ return Criteria.matchEthSrc(mac);
+ }
+ }
+
+ private class InPortDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ PortNumber port = PortNumber.portNumber(nullIsIllegal(json.get(CriterionCodec.PORT),
+ CriterionCodec.PORT + MISSING_MEMBER_MESSAGE).asLong());
+
+ return Criteria.matchInPort(port);
+ }
+ }
+
+ private class InPhyPortDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ PortNumber port = PortNumber.portNumber(nullIsIllegal(json.get(CriterionCodec.PORT),
+ CriterionCodec.PORT + MISSING_MEMBER_MESSAGE).asLong());
+
+ return Criteria.matchInPhyPort(port);
+ }
+ }
+
+ private class MetadataDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ long metadata = nullIsIllegal(json.get(CriterionCodec.METADATA),
+ CriterionCodec.METADATA + MISSING_MEMBER_MESSAGE).asLong();
+
+ return Criteria.matchMetadata(metadata);
+ }
+ }
+
+ private class VlanVidDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ short vlanId = (short) nullIsIllegal(json.get(CriterionCodec.VLAN_ID),
+ CriterionCodec.VLAN_ID + MISSING_MEMBER_MESSAGE).asInt();
+
+ return Criteria.matchVlanId(VlanId.vlanId(vlanId));
+ }
+ }
+
+ private class VlanPcpDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ byte priority = (byte) nullIsIllegal(json.get(CriterionCodec.PRIORITY),
+ CriterionCodec.VLAN_ID + MISSING_MEMBER_MESSAGE).asInt();
+
+ return Criteria.matchVlanPcp(priority);
+ }
+ }
+
+ private class IpDscpDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ byte ipDscp = (byte) nullIsIllegal(json.get(CriterionCodec.IP_DSCP),
+ CriterionCodec.IP_DSCP + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIPDscp(ipDscp);
+ }
+ }
+
+ private class IpEcnDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ byte ipEcn = (byte) nullIsIllegal(json.get(CriterionCodec.IP_ECN),
+ CriterionCodec.IP_ECN + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIPEcn(ipEcn);
+ }
+ }
+
+ private class IpProtoDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ short proto = (short) nullIsIllegal(json.get(CriterionCodec.PROTOCOL),
+ CriterionCodec.PROTOCOL + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIPProtocol(proto);
+ }
+ }
+
+ private class IpV4SrcDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ String ip = nullIsIllegal(json.get(CriterionCodec.IP),
+ CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText();
+ return Criteria.matchIPSrc(IpPrefix.valueOf(ip));
+ }
+ }
+
+ private class IpV4DstDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ String ip = nullIsIllegal(json.get(CriterionCodec.IP),
+ CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText();
+ return Criteria.matchIPDst(IpPrefix.valueOf(ip));
+ }
+ }
+
+ private class IpV6SrcDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ String ip = nullIsIllegal(json.get(CriterionCodec.IP),
+ CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText();
+ return Criteria.matchIPv6Src(IpPrefix.valueOf(ip));
+ }
+ }
+
+ private class IpV6DstDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ String ip = nullIsIllegal(json.get(CriterionCodec.IP),
+ CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText();
+ return Criteria.matchIPv6Dst(IpPrefix.valueOf(ip));
+ }
+ }
+
+ private class TcpSrcDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.TCP_PORT),
+ CriterionCodec.TCP_PORT + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchTcpSrc(tcpPort);
+ }
+ }
+
+ private class TcpDstDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.TCP_PORT),
+ CriterionCodec.TCP_PORT + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchTcpDst(tcpPort);
+ }
+ }
+
+ private class UdpSrcDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.UDP_PORT),
+ CriterionCodec.UDP_PORT + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchUdpSrc(udpPort);
+ }
+ }
+
+ private class UdpDstDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.UDP_PORT),
+ CriterionCodec.UDP_PORT + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchUdpDst(udpPort);
+ }
+ }
+
+ private class SctpSrcDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ TpPort sctpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.SCTP_PORT),
+ CriterionCodec.SCTP_PORT + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchSctpSrc(sctpPort);
+ }
+ }
+
+ private class SctpDstDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ TpPort sctpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.SCTP_PORT),
+ CriterionCodec.SCTP_PORT + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchSctpDst(sctpPort);
+ }
+ }
+
+ private class IcmpV4TypeDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ short type = (short) nullIsIllegal(json.get(CriterionCodec.ICMP_TYPE),
+ CriterionCodec.ICMP_TYPE + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIcmpType(type);
+ }
+ }
+
+ private class IcmpV4CodeDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ short code = (short) nullIsIllegal(json.get(CriterionCodec.ICMP_CODE),
+ CriterionCodec.ICMP_CODE + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIcmpCode(code);
+ }
+ }
+
+ private class IpV6FLabelDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ int flowLabel = nullIsIllegal(json.get(CriterionCodec.FLOW_LABEL),
+ CriterionCodec.FLOW_LABEL + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIPv6FlowLabel(flowLabel);
+ }
+ }
+
+ private class IcmpV6TypeDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ short type = (short) nullIsIllegal(json.get(CriterionCodec.ICMPV6_TYPE),
+ CriterionCodec.ICMPV6_TYPE + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIcmpv6Type(type);
+ }
+ }
+
+ private class IcmpV6CodeDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ short code = (short) nullIsIllegal(json.get(CriterionCodec.ICMPV6_CODE),
+ CriterionCodec.ICMPV6_CODE + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIcmpv6Code(code);
+ }
+ }
+
+ private class V6NDTargetDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ Ip6Address target = Ip6Address.valueOf(nullIsIllegal(json.get(CriterionCodec.TARGET_ADDRESS),
+ CriterionCodec.TARGET_ADDRESS + MISSING_MEMBER_MESSAGE).asText());
+ return Criteria.matchIPv6NDTargetAddress(target);
+ }
+ }
+
+ private class V6NDSllDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC),
+ CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText());
+ return Criteria.matchIPv6NDSourceLinkLayerAddress(mac);
+ }
+ }
+
+ private class V6NDTllDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC),
+ CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText());
+ return Criteria.matchIPv6NDTargetLinkLayerAddress(mac);
+ }
+ }
+
+ private class MplsLabelDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ int label = nullIsIllegal(json.get(CriterionCodec.LABEL),
+ CriterionCodec.LABEL + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchMplsLabel(MplsLabel.mplsLabel(label));
+ }
+ }
+
+ private class IpV6ExthdrDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ int exthdrFlags = nullIsIllegal(json.get(CriterionCodec.EXT_HDR_FLAGS),
+ CriterionCodec.EXT_HDR_FLAGS + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchIPv6ExthdrFlags(exthdrFlags);
+ }
+ }
+
+ private class OchSigIdDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ if (json.get(CriterionCodec.LAMBDA) != null) {
+ Lambda lambda = Lambda.indexedLambda(nullIsIllegal(json.get(CriterionCodec.LAMBDA),
+ CriterionCodec.LAMBDA + MISSING_MEMBER_MESSAGE).asInt());
+ return Criteria.matchLambda(lambda);
+ } else {
+ JsonNode ochSignalId = nullIsIllegal(json.get(CriterionCodec.OCH_SIGNAL_ID),
+ CriterionCodec.GRID_TYPE + MISSING_MEMBER_MESSAGE);
+ GridType gridType =
+ GridType.valueOf(
+ nullIsIllegal(ochSignalId.get(CriterionCodec.GRID_TYPE),
+ CriterionCodec.GRID_TYPE + MISSING_MEMBER_MESSAGE).asText());
+ ChannelSpacing channelSpacing =
+ ChannelSpacing.valueOf(
+ nullIsIllegal(ochSignalId.get(CriterionCodec.CHANNEL_SPACING),
+ CriterionCodec.CHANNEL_SPACING + MISSING_MEMBER_MESSAGE).asText());
+ int spacingMultiplier = nullIsIllegal(ochSignalId.get(CriterionCodec.SPACING_MULIPLIER),
+ CriterionCodec.SPACING_MULIPLIER + MISSING_MEMBER_MESSAGE).asInt();
+ int slotGranularity = nullIsIllegal(ochSignalId.get(CriterionCodec.SLOT_GRANULARITY),
+ CriterionCodec.SLOT_GRANULARITY + MISSING_MEMBER_MESSAGE).asInt();
+ return Criteria.matchLambda(
+ Lambda.ochSignal(gridType, channelSpacing,
+ spacingMultiplier, slotGranularity));
+ }
+ }
+ }
+
+ private class OchSigTypeDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ return null;
+ }
+ }
+
+ private class TunnelIdDecoder implements CriterionDecoder {
+ @Override
+ public Criterion decodeCriterion(ObjectNode json) {
+ long tunnelId = nullIsIllegal(json.get(CriterionCodec.TUNNEL_ID),
+ CriterionCodec.TUNNEL_ID + MISSING_MEMBER_MESSAGE).asLong();
+ return Criteria.matchTunnelId(tunnelId);
+ }
+ }
+
+ /**
+ * Decodes the JSON into a criterion object.
+ *
+ * @return Criterion object
+ * @throws IllegalArgumentException if the JSON is invalid
+ */
+ public Criterion decode() {
+ String type = json.get(CriterionCodec.TYPE).asText();
+
+ CriterionDecoder decoder = decoderMap.get(type);
+ if (decoder != null) {
+ return decoder.decodeCriterion(json);
+ }
+
+ throw new IllegalArgumentException("Type " + type + " is unknown");
+ }
+
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
new file mode 100644
index 00000000..6a97a076
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
@@ -0,0 +1,235 @@
+/*
+ * 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.codec.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+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.flow.instructions.L4ModificationInstruction;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Decoding portion of the instruction codec.
+ */
+public final class DecodeInstructionCodecHelper {
+ private final ObjectNode json;
+
+ /**
+ * Creates a decode instruction codec object.
+ *
+ * @param json JSON object to decode
+ */
+ public DecodeInstructionCodecHelper(ObjectNode json) {
+ this.json = json;
+ }
+
+ /**
+ * Decodes a Layer 2 instruction.
+ *
+ * @return instruction object decoded from the JSON
+ * @throws IllegalArgumentException if the JSON is invalid
+ */
+ private Instruction decodeL2() {
+ String subType = json.get(InstructionCodec.SUBTYPE).asText();
+
+ if (subType.equals(L2ModificationInstruction.L2SubType.ETH_SRC.name())) {
+ String mac = nullIsIllegal(json.get(InstructionCodec.MAC),
+ InstructionCodec.MAC + InstructionCodec.MISSING_MEMBER_MESSAGE).asText();
+ return Instructions.modL2Src(MacAddress.valueOf(mac));
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.ETH_DST.name())) {
+ String mac = nullIsIllegal(json.get(InstructionCodec.MAC),
+ InstructionCodec.MAC + InstructionCodec.MISSING_MEMBER_MESSAGE).asText();
+ return Instructions.modL2Dst(MacAddress.valueOf(mac));
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_ID.name())) {
+ short vlanId = (short) nullIsIllegal(json.get(InstructionCodec.VLAN_ID),
+ InstructionCodec.VLAN_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ return Instructions.modVlanId(VlanId.vlanId(vlanId));
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_PCP.name())) {
+ byte vlanPcp = (byte) nullIsIllegal(json.get(InstructionCodec.VLAN_PCP),
+ InstructionCodec.VLAN_PCP + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ return Instructions.modVlanPcp(vlanPcp);
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.MPLS_LABEL.name())) {
+ int label = nullIsIllegal(json.get(InstructionCodec.MPLS_LABEL),
+ InstructionCodec.MPLS_LABEL + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ return Instructions.modMplsLabel(MplsLabel.mplsLabel(label));
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.MPLS_PUSH.name())) {
+ return Instructions.pushMpls();
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.MPLS_POP.name())) {
+ return Instructions.popMpls();
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.DEC_MPLS_TTL.name())) {
+ return Instructions.decMplsTtl();
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_POP.name())) {
+ return Instructions.popVlan();
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_PUSH.name())) {
+ return Instructions.pushVlan();
+ } else if (subType.equals(L2ModificationInstruction.L2SubType.TUNNEL_ID.name())) {
+ long tunnelId = nullIsIllegal(json.get(InstructionCodec.TUNNEL_ID),
+ InstructionCodec.TUNNEL_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asLong();
+ return Instructions.modTunnelId(tunnelId);
+ }
+ throw new IllegalArgumentException("L2 Instruction subtype "
+ + subType + " is not supported");
+ }
+
+ /**
+ * Decodes a Layer 3 instruction.
+ *
+ * @return instruction object decoded from the JSON
+ * @throws IllegalArgumentException if the JSON is invalid
+ */
+ private Instruction decodeL3() {
+ String subType = json.get(InstructionCodec.SUBTYPE).asText();
+
+ if (subType.equals(L3ModificationInstruction.L3SubType.IPV4_SRC.name())) {
+ IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP),
+ InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText());
+ return Instructions.modL3Src(ip);
+ } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV4_DST.name())) {
+ IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP),
+ InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText());
+ return Instructions.modL3Dst(ip);
+ } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV6_SRC.name())) {
+ IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP),
+ InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText());
+ return Instructions.modL3IPv6Src(ip);
+ } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV6_DST.name())) {
+ IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP),
+ InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText());
+ return Instructions.modL3IPv6Dst(ip);
+ } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV6_FLABEL.name())) {
+ int flowLabel = nullIsIllegal(json.get(InstructionCodec.FLOW_LABEL),
+ InstructionCodec.FLOW_LABEL + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ return Instructions.modL3IPv6FlowLabel(flowLabel);
+ }
+ throw new IllegalArgumentException("L3 Instruction subtype "
+ + subType + " is not supported");
+ }
+
+ /**
+ * Decodes a Layer 0 instruction.
+ *
+ * @return instruction object decoded from the JSON
+ * @throws IllegalArgumentException if the JSON is invalid
+ */
+ private Instruction decodeL0() {
+ String subType = json.get(InstructionCodec.SUBTYPE).asText();
+
+
+ if (subType.equals(L0ModificationInstruction.L0SubType.LAMBDA.name())) {
+ int lambda = nullIsIllegal(json.get(InstructionCodec.LAMBDA),
+ InstructionCodec.LAMBDA + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ return Instructions.modL0Lambda(Lambda.indexedLambda(lambda));
+ } else if (subType.equals(L0ModificationInstruction.L0SubType.OCH.name())) {
+ String gridTypeString = nullIsIllegal(json.get(InstructionCodec.GRID_TYPE),
+ InstructionCodec.GRID_TYPE + InstructionCodec.MISSING_MEMBER_MESSAGE).asText();
+ GridType gridType = GridType.valueOf(gridTypeString);
+ if (gridType == null) {
+ throw new IllegalArgumentException("Unknown grid type "
+ + gridTypeString);
+ }
+ String channelSpacingString = nullIsIllegal(json.get(InstructionCodec.CHANNEL_SPACING),
+ InstructionCodec.CHANNEL_SPACING + InstructionCodec.MISSING_MEMBER_MESSAGE).asText();
+ ChannelSpacing channelSpacing = ChannelSpacing.valueOf(channelSpacingString);
+ if (channelSpacing == null) {
+ throw new IllegalArgumentException("Unknown channel spacing "
+ + channelSpacingString);
+ }
+ int spacingMultiplier = nullIsIllegal(json.get(InstructionCodec.SPACING_MULTIPLIER),
+ InstructionCodec.SPACING_MULTIPLIER + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ int slotGranularity = nullIsIllegal(json.get(InstructionCodec.SLOT_GRANULARITY),
+ InstructionCodec.SLOT_GRANULARITY + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt();
+ return Instructions.modL0Lambda(new OchSignal(gridType, channelSpacing,
+ spacingMultiplier, slotGranularity));
+ }
+ throw new IllegalArgumentException("L0 Instruction subtype "
+ + subType + " is not supported");
+ }
+
+ /**
+ * Decodes a Layer 4 instruction.
+ *
+ * @return instruction object decoded from the JSON
+ * @throws IllegalArgumentException if the JSON is invalid
+ */
+ private Instruction decodeL4() {
+ String subType = json.get(InstructionCodec.SUBTYPE).asText();
+
+ if (subType.equals(L4ModificationInstruction.L4SubType.TCP_DST.name())) {
+ TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.TCP_PORT),
+ InstructionCodec.TCP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+ return Instructions.modTcpDst(tcpPort);
+ } else if (subType.equals(L4ModificationInstruction.L4SubType.TCP_SRC.name())) {
+ TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.TCP_PORT),
+ InstructionCodec.TCP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+ return Instructions.modTcpSrc(tcpPort);
+ } else if (subType.equals(L4ModificationInstruction.L4SubType.UDP_DST.name())) {
+ TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.UDP_PORT),
+ InstructionCodec.UDP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+ return Instructions.modUdpDst(udpPort);
+ } else if (subType.equals(L4ModificationInstruction.L4SubType.UDP_SRC.name())) {
+ TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.UDP_PORT),
+ InstructionCodec.UDP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+ return Instructions.modUdpSrc(udpPort);
+ }
+ throw new IllegalArgumentException("L4 Instruction subtype "
+ + subType + " is not supported");
+ }
+
+ /**
+ * Decodes the JSON into an instruction object.
+ *
+ * @return Criterion object
+ * @throws IllegalArgumentException if the JSON is invalid
+ */
+ public Instruction decode() {
+ String type = json.get(InstructionCodec.TYPE).asText();
+
+ if (type.equals(Instruction.Type.OUTPUT.name())) {
+ PortNumber portNumber =
+ PortNumber.portNumber(nullIsIllegal(json.get(InstructionCodec.PORT),
+ InstructionCodec.PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asLong());
+ return Instructions.createOutput(portNumber);
+ } else if (type.equals(Instruction.Type.DROP.name())) {
+ return Instructions.createDrop();
+ } else if (type.equals(Instruction.Type.L0MODIFICATION.name())) {
+ return decodeL0();
+ } else if (type.equals(Instruction.Type.L2MODIFICATION.name())) {
+ return decodeL2();
+ } else if (type.equals(Instruction.Type.L3MODIFICATION.name())) {
+ return decodeL3();
+ } else if (type.equals(Instruction.Type.L4MODIFICATION.name())) {
+ return decodeL4();
+ }
+ throw new IllegalArgumentException("Instruction type "
+ + type + " is not supported");
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
new file mode 100644
index 00000000..f1a4f786
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
@@ -0,0 +1,93 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onlab.packet.ChassisId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.Annotations;
+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.device.DeviceService;
+import org.onosproject.net.provider.ProviderId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.DeviceId.deviceId;
+
+/**
+ * Device JSON codec.
+ */
+public final class DeviceCodec extends AnnotatedCodec<Device> {
+
+ // JSON fieldNames
+ private static final String ID = "id";
+ private static final String TYPE = "type";
+ private static final String MFR = "mfr";
+ private static final String HW = "hw";
+ private static final String SW = "sw";
+ private static final String SERIAL = "serial";
+ private static final String CHASSIS_ID = "chassisId";
+
+
+ @Override
+ public ObjectNode encode(Device device, CodecContext context) {
+ checkNotNull(device, "Device cannot be null");
+ DeviceService service = context.getService(DeviceService.class);
+ ObjectNode result = context.mapper().createObjectNode()
+ .put(ID, device.id().toString())
+ .put(TYPE, device.type().name())
+ .put("available", service.isAvailable(device.id()))
+ .put("role", service.getRole(device.id()).toString())
+ .put(MFR, device.manufacturer())
+ .put(HW, device.hwVersion())
+ .put(SW, device.swVersion())
+ .put(SERIAL, device.serialNumber())
+ .put(CHASSIS_ID, device.chassisId().toString());
+ return annotate(result, device, context);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Note: ProviderId is not part of JSON representation.
+ * Returned object will have random ProviderId set.
+ */
+ @Override
+ public Device decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ DeviceId id = deviceId(json.get(ID).asText());
+ // TODO: add providerId to JSON if we need to recover them.
+ ProviderId pid = new ProviderId(id.uri().getScheme(), "DeviceCodec");
+
+ Type type = Type.valueOf(json.get(TYPE).asText());
+ String mfr = json.get(MFR).asText();
+ String hw = json.get(HW).asText();
+ String sw = json.get(SW).asText();
+ String serial = json.get(SERIAL).asText();
+ ChassisId chassisId = new ChassisId(json.get(CHASSIS_ID).asText());
+ Annotations annotations = extractAnnotations(json, context);
+
+ return new DefaultDevice(pid, id, type, mfr, hw, sw, serial,
+ chassisId, annotations);
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java
new file mode 100644
index 00000000..4935d992
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java
@@ -0,0 +1,78 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.driver.Driver;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * JSON codec for the Driver class.
+ */
+public final class DriverCodec extends JsonCodec<Driver> {
+ private static final String PARENT = "parent";
+ private static final String NAME = "name";
+ private static final String MANUFACTURER = "manufacturer";
+ private static final String HW_VERSION = "hwVersion";
+ private static final String SW_VERSION = "swVersion";
+ private static final String BEHAVIOURS = "behaviours";
+ private static final String BEHAVIORS_NAME = "name";
+ private static final String BEHAVIORS_IMPLEMENTATION_NAME = "implementationName";
+ private static final String PROPERTIES = "properties";
+
+ @Override
+ public ObjectNode encode(Driver driver, CodecContext context) {
+ checkNotNull(driver, "Driver cannot be null");
+
+ ObjectNode result = context.mapper().createObjectNode()
+ .put(NAME, driver.name())
+ .put(MANUFACTURER, driver.manufacturer())
+ .put(HW_VERSION, driver.hwVersion())
+ .put(SW_VERSION, driver.swVersion());
+
+ if (driver.parent() != null) {
+ result.put(PARENT, driver.parent().name());
+ }
+
+ ArrayNode behaviours = context.mapper().createArrayNode();
+ driver.behaviours().forEach(behaviour -> {
+ ObjectNode entry = context.mapper().createObjectNode()
+ .put(BEHAVIORS_NAME, behaviour.getCanonicalName())
+ .put(BEHAVIORS_IMPLEMENTATION_NAME,
+ driver.implementation(behaviour).getCanonicalName());
+
+ behaviours.add(entry);
+ });
+ result.set(BEHAVIOURS, behaviours);
+
+ ArrayNode properties = context.mapper().createArrayNode();
+ driver.properties().forEach((name, value) -> {
+ ObjectNode entry = context.mapper().createObjectNode()
+ .put("name", name)
+ .put("value", value);
+
+ properties.add(entry);
+ });
+ result.set(PROPERTIES, properties);
+
+ return result;
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java
new file mode 100644
index 00000000..61f4dbf4
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java
@@ -0,0 +1,201 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.LinkTypeConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of encoder for constraint JSON codec.
+ */
+public final class EncodeConstraintCodecHelper {
+
+ private final Constraint constraint;
+ private final CodecContext context;
+
+ /**
+ * Constructs a constraint encoder.
+ *
+ * @param constraint constraint to encode
+ * @param context to use for look ups
+ */
+ public EncodeConstraintCodecHelper(Constraint constraint, CodecContext context) {
+ this.constraint = constraint;
+ this.context = context;
+ }
+
+ /**
+ * Encodes a latency constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeLatencyConstraint() {
+ checkNotNull(constraint, "Duration constraint cannot be null");
+ final LatencyConstraint latencyConstraint =
+ (LatencyConstraint) constraint;
+ return context.mapper().createObjectNode()
+ .put("latencyMillis", latencyConstraint.latency().toMillis());
+ }
+
+ /**
+ * Encodes an obstacle constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeObstacleConstraint() {
+ checkNotNull(constraint, "Obstacle constraint cannot be null");
+ final ObstacleConstraint obstacleConstraint =
+ (ObstacleConstraint) constraint;
+
+ final ObjectNode result = context.mapper().createObjectNode();
+ final ArrayNode jsonObstacles = result.putArray("obstacles");
+
+ for (DeviceId did : obstacleConstraint.obstacles()) {
+ jsonObstacles.add(did.toString());
+ }
+
+ return result;
+ }
+
+ /**
+ * Encodes a waypoint constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeWaypointConstraint() {
+ checkNotNull(constraint, "Waypoint constraint cannot be null");
+ final WaypointConstraint waypointConstraint =
+ (WaypointConstraint) constraint;
+
+ final ObjectNode result = context.mapper().createObjectNode();
+ final ArrayNode jsonWaypoints = result.putArray("waypoints");
+
+ for (DeviceId did : waypointConstraint.waypoints()) {
+ jsonWaypoints.add(did.toString());
+ }
+
+ return result;
+ }
+
+ /**
+ * Encodes a annotation constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeAnnotationConstraint() {
+ checkNotNull(constraint, "Annotation constraint cannot be null");
+ final AnnotationConstraint annotationConstraint =
+ (AnnotationConstraint) constraint;
+ return context.mapper().createObjectNode()
+ .put("key", annotationConstraint.key())
+ .put("threshold", annotationConstraint.threshold());
+ }
+
+ /**
+ * Encodes a bandwidth constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeBandwidthConstraint() {
+ checkNotNull(constraint, "Bandwidth constraint cannot be null");
+ final BandwidthConstraint bandwidthConstraint =
+ (BandwidthConstraint) constraint;
+ return context.mapper().createObjectNode()
+ .put("bandwidth", bandwidthConstraint.bandwidth().toDouble());
+ }
+
+ /**
+ * Encodes a lambda constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeLambdaConstraint() {
+ checkNotNull(constraint, "Lambda constraint cannot be null");
+ final LambdaConstraint lambdaConstraint =
+ (LambdaConstraint) constraint;
+
+ return context.mapper().createObjectNode()
+ .put("lambda", lambdaConstraint.lambda().toInt());
+ }
+
+ /**
+ * Encodes a link type constraint.
+ *
+ * @return JSON ObjectNode representing the constraint
+ */
+ private ObjectNode encodeLinkTypeConstraint() {
+ checkNotNull(constraint, "Link type constraint cannot be null");
+
+ final LinkTypeConstraint linkTypeConstraint =
+ (LinkTypeConstraint) constraint;
+
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put(ConstraintCodec.INCLUSIVE, linkTypeConstraint.isInclusive());
+
+ final ArrayNode jsonTypes = result.putArray(ConstraintCodec.TYPES);
+
+ if (linkTypeConstraint.types() != null) {
+ for (Link.Type type : linkTypeConstraint.types()) {
+ jsonTypes.add(type.name());
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Encodes the constraint in JSON.
+ *
+ * @return JSON node
+ */
+ public ObjectNode encode() {
+ final ObjectNode result;
+ if (constraint instanceof BandwidthConstraint) {
+ result = encodeBandwidthConstraint();
+ } else if (constraint instanceof LambdaConstraint) {
+ result = encodeLambdaConstraint();
+ } else if (constraint instanceof LinkTypeConstraint) {
+ result = encodeLinkTypeConstraint();
+ } else if (constraint instanceof AnnotationConstraint) {
+ result = encodeAnnotationConstraint();
+ } else if (constraint instanceof LatencyConstraint) {
+ result = encodeLatencyConstraint();
+ } else if (constraint instanceof ObstacleConstraint) {
+ result = encodeObstacleConstraint();
+ } else if (constraint instanceof WaypointConstraint) {
+ result = encodeWaypointConstraint();
+ } else {
+ result = context.mapper().createObjectNode();
+ }
+
+ result.put(ConstraintCodec.TYPE, constraint.getClass().getSimpleName());
+ return result;
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java
new file mode 100644
index 00000000..a962c0dd
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java
@@ -0,0 +1,396 @@
+/*
+ * 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.codec.impl;
+
+import java.util.EnumMap;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.IPDscpCriterion;
+import org.onosproject.net.flow.criteria.IPEcnCriterion;
+import org.onosproject.net.flow.criteria.IPProtocolCriterion;
+import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion;
+import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion;
+import org.onosproject.net.flow.criteria.IcmpCodeCriterion;
+import org.onosproject.net.flow.criteria.IcmpTypeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion;
+import org.onosproject.net.flow.criteria.MetadataCriterion;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.SctpPortCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.TunnelIdCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.criteria.VlanPcpCriterion;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode portion of the criterion codec.
+ */
+public final class EncodeCriterionCodecHelper {
+
+ private final Criterion criterion;
+ private final CodecContext context;
+
+ private final EnumMap<Criterion.Type, CriterionTypeFormatter> formatMap;
+
+ /**
+ * Creates an encoder object for a criterion.
+ * Initializes the formatter lookup map for the criterion subclasses.
+ *
+ * @param criterion Criterion to encode
+ * @param context context of the JSON encoding
+ */
+ public EncodeCriterionCodecHelper(Criterion criterion, CodecContext context) {
+ this.criterion = criterion;
+ this.context = context;
+
+ formatMap = new EnumMap<>(Criterion.Type.class);
+
+ formatMap.put(Criterion.Type.IN_PORT, new FormatInPort());
+ formatMap.put(Criterion.Type.IN_PHY_PORT, new FormatInPort());
+ formatMap.put(Criterion.Type.METADATA, new FormatMetadata());
+ formatMap.put(Criterion.Type.ETH_DST, new FormatEth());
+ formatMap.put(Criterion.Type.ETH_SRC, new FormatEth());
+ formatMap.put(Criterion.Type.ETH_TYPE, new FormatEthType());
+ formatMap.put(Criterion.Type.VLAN_VID, new FormatVlanVid());
+ formatMap.put(Criterion.Type.VLAN_PCP, new FormatVlanPcp());
+ formatMap.put(Criterion.Type.IP_DSCP, new FormatIpDscp());
+ formatMap.put(Criterion.Type.IP_ECN, new FormatIpEcn());
+ formatMap.put(Criterion.Type.IP_PROTO, new FormatIpProto());
+ formatMap.put(Criterion.Type.IPV4_SRC, new FormatIp());
+ formatMap.put(Criterion.Type.IPV4_DST, new FormatIp());
+ formatMap.put(Criterion.Type.TCP_SRC, new FormatTcp());
+ formatMap.put(Criterion.Type.TCP_DST, new FormatTcp());
+ formatMap.put(Criterion.Type.UDP_SRC, new FormatUdp());
+ formatMap.put(Criterion.Type.UDP_DST, new FormatUdp());
+ formatMap.put(Criterion.Type.SCTP_SRC, new FormatSctp());
+ formatMap.put(Criterion.Type.SCTP_DST, new FormatSctp());
+ formatMap.put(Criterion.Type.ICMPV4_TYPE, new FormatIcmpV4Type());
+ formatMap.put(Criterion.Type.ICMPV4_CODE, new FormatIcmpV4Code());
+ formatMap.put(Criterion.Type.IPV6_SRC, new FormatIp());
+ formatMap.put(Criterion.Type.IPV6_DST, new FormatIp());
+ formatMap.put(Criterion.Type.IPV6_FLABEL, new FormatIpV6FLabel());
+ formatMap.put(Criterion.Type.ICMPV6_TYPE, new FormatIcmpV6Type());
+ formatMap.put(Criterion.Type.ICMPV6_CODE, new FormatIcmpV6Code());
+ formatMap.put(Criterion.Type.IPV6_ND_TARGET, new FormatV6NDTarget());
+ formatMap.put(Criterion.Type.IPV6_ND_SLL, new FormatV6NDTll());
+ formatMap.put(Criterion.Type.IPV6_ND_TLL, new FormatV6NDTll());
+ formatMap.put(Criterion.Type.MPLS_LABEL, new FormatMplsLabel());
+ formatMap.put(Criterion.Type.IPV6_EXTHDR, new FormatIpV6Exthdr());
+ formatMap.put(Criterion.Type.OCH_SIGID, new FormatOchSigId());
+ formatMap.put(Criterion.Type.OCH_SIGTYPE, new FormatOchSigType());
+ formatMap.put(Criterion.Type.TUNNEL_ID, new FormatTunnelId());
+ formatMap.put(Criterion.Type.DUMMY, new FormatDummyType());
+
+ // Currently unimplemented
+ formatMap.put(Criterion.Type.ARP_OP, new FormatUnknown());
+ formatMap.put(Criterion.Type.ARP_SPA, new FormatUnknown());
+ formatMap.put(Criterion.Type.ARP_TPA, new FormatUnknown());
+ formatMap.put(Criterion.Type.ARP_SHA, new FormatUnknown());
+ formatMap.put(Criterion.Type.ARP_THA, new FormatUnknown());
+ formatMap.put(Criterion.Type.MPLS_TC, new FormatUnknown());
+ formatMap.put(Criterion.Type.MPLS_BOS, new FormatUnknown());
+ formatMap.put(Criterion.Type.PBB_ISID, new FormatUnknown());
+ formatMap.put(Criterion.Type.UNASSIGNED_40, new FormatUnknown());
+ formatMap.put(Criterion.Type.PBB_UCA, new FormatUnknown());
+ formatMap.put(Criterion.Type.TCP_FLAGS, new FormatUnknown());
+ formatMap.put(Criterion.Type.ACTSET_OUTPUT, new FormatUnknown());
+ formatMap.put(Criterion.Type.PACKET_TYPE, new FormatUnknown());
+ }
+
+ private interface CriterionTypeFormatter {
+ ObjectNode encodeCriterion(ObjectNode root, Criterion criterion);
+ }
+
+ private static class FormatUnknown implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ return root;
+ }
+ }
+
+ private static class FormatInPort implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final PortCriterion portCriterion = (PortCriterion) criterion;
+ return root.put(CriterionCodec.PORT, portCriterion.port().toLong());
+ }
+ }
+
+ private static class FormatMetadata implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final MetadataCriterion metadataCriterion =
+ (MetadataCriterion) criterion;
+ return root.put(CriterionCodec.METADATA, metadataCriterion.metadata());
+ }
+ }
+
+ private static class FormatEth implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final EthCriterion ethCriterion = (EthCriterion) criterion;
+ return root.put(CriterionCodec.MAC, ethCriterion.mac().toString());
+ }
+ }
+
+ private static class FormatEthType implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final EthTypeCriterion ethTypeCriterion =
+ (EthTypeCriterion) criterion;
+ return root.put(CriterionCodec.ETH_TYPE, ethTypeCriterion.ethType().toShort());
+ }
+ }
+
+ private static class FormatVlanVid implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final VlanIdCriterion vlanIdCriterion =
+ (VlanIdCriterion) criterion;
+ return root.put(CriterionCodec.VLAN_ID, vlanIdCriterion.vlanId().toShort());
+ }
+ }
+
+ private static class FormatVlanPcp implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final VlanPcpCriterion vlanPcpCriterion =
+ (VlanPcpCriterion) criterion;
+ return root.put(CriterionCodec.PRIORITY, vlanPcpCriterion.priority());
+ }
+ }
+
+ private static class FormatIpDscp implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPDscpCriterion ipDscpCriterion =
+ (IPDscpCriterion) criterion;
+ return root.put(CriterionCodec.IP_DSCP, ipDscpCriterion.ipDscp());
+ }
+ }
+
+ private static class FormatIpEcn implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPEcnCriterion ipEcnCriterion =
+ (IPEcnCriterion) criterion;
+ return root.put(CriterionCodec.IP_ECN, ipEcnCriterion.ipEcn());
+ }
+ }
+
+ private static class FormatIpProto implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPProtocolCriterion iPProtocolCriterion =
+ (IPProtocolCriterion) criterion;
+ return root.put(CriterionCodec.PROTOCOL, iPProtocolCriterion.protocol());
+ }
+ }
+
+ private static class FormatIp implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPCriterion iPCriterion = (IPCriterion) criterion;
+ return root.put(CriterionCodec.IP, iPCriterion.ip().toString());
+ }
+ }
+
+ private static class FormatTcp implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final TcpPortCriterion tcpPortCriterion =
+ (TcpPortCriterion) criterion;
+ return root.put(CriterionCodec.TCP_PORT, tcpPortCriterion.tcpPort().toInt());
+ }
+ }
+
+ private static class FormatUdp implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final UdpPortCriterion udpPortCriterion =
+ (UdpPortCriterion) criterion;
+ return root.put(CriterionCodec.UDP_PORT, udpPortCriterion.udpPort().toInt());
+ }
+ }
+
+ private static class FormatSctp implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final SctpPortCriterion sctpPortCriterion =
+ (SctpPortCriterion) criterion;
+ return root.put(CriterionCodec.SCTP_PORT, sctpPortCriterion.sctpPort().toInt());
+ }
+ }
+
+ private static class FormatIcmpV4Type implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IcmpTypeCriterion icmpTypeCriterion =
+ (IcmpTypeCriterion) criterion;
+ return root.put(CriterionCodec.ICMP_TYPE, icmpTypeCriterion.icmpType());
+ }
+ }
+
+ private static class FormatIcmpV4Code implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IcmpCodeCriterion icmpCodeCriterion =
+ (IcmpCodeCriterion) criterion;
+ return root.put(CriterionCodec.ICMP_CODE, icmpCodeCriterion.icmpCode());
+ }
+ }
+
+ private static class FormatIpV6FLabel implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPv6FlowLabelCriterion ipv6FlowLabelCriterion =
+ (IPv6FlowLabelCriterion) criterion;
+ return root.put(CriterionCodec.FLOW_LABEL, ipv6FlowLabelCriterion.flowLabel());
+ }
+ }
+
+ private static class FormatIcmpV6Type implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final Icmpv6TypeCriterion icmpv6TypeCriterion =
+ (Icmpv6TypeCriterion) criterion;
+ return root.put(CriterionCodec.ICMPV6_TYPE, icmpv6TypeCriterion.icmpv6Type());
+ }
+ }
+
+ private static class FormatIcmpV6Code implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final Icmpv6CodeCriterion icmpv6CodeCriterion =
+ (Icmpv6CodeCriterion) criterion;
+ return root.put(CriterionCodec.ICMPV6_CODE, icmpv6CodeCriterion.icmpv6Code());
+ }
+ }
+
+ private static class FormatV6NDTarget implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPv6NDTargetAddressCriterion ipv6NDTargetAddressCriterion
+ = (IPv6NDTargetAddressCriterion) criterion;
+ return root.put(CriterionCodec.TARGET_ADDRESS, ipv6NDTargetAddressCriterion.targetAddress().toString());
+ }
+ }
+
+ private static class FormatV6NDTll implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPv6NDLinkLayerAddressCriterion ipv6NDLinkLayerAddressCriterion
+ = (IPv6NDLinkLayerAddressCriterion) criterion;
+ return root.put(CriterionCodec.MAC, ipv6NDLinkLayerAddressCriterion.mac().toString());
+ }
+ }
+
+ private static class FormatMplsLabel implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final MplsCriterion mplsCriterion =
+ (MplsCriterion) criterion;
+ return root.put(CriterionCodec.LABEL, mplsCriterion.label().toInt());
+ }
+ }
+
+ private static class FormatIpV6Exthdr implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final IPv6ExthdrFlagsCriterion exthdrCriterion =
+ (IPv6ExthdrFlagsCriterion) criterion;
+ return root.put(CriterionCodec.EXT_HDR_FLAGS, exthdrCriterion.exthdrFlags());
+ }
+ }
+
+ private static class FormatOchSigId implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ OchSignal ochSignal = ((OchSignalCriterion) criterion).lambda();
+ ObjectNode child = root.putObject(CriterionCodec.OCH_SIGNAL_ID);
+
+ child.put(CriterionCodec.GRID_TYPE, ochSignal.gridType().name());
+ child.put(CriterionCodec.CHANNEL_SPACING, ochSignal.channelSpacing().name());
+ child.put(CriterionCodec.SPACING_MULIPLIER, ochSignal.spacingMultiplier());
+ child.put(CriterionCodec.SLOT_GRANULARITY, ochSignal.slotGranularity());
+
+ return root;
+ }
+ }
+
+ private static class FormatOchSigType implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final OchSignalTypeCriterion ochSignalTypeCriterion =
+ (OchSignalTypeCriterion) criterion;
+ return root.put("ochSignalType", ochSignalTypeCriterion.signalType().name());
+ }
+ }
+
+ private static class FormatTunnelId implements CriterionTypeFormatter {
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ final TunnelIdCriterion tunnelIdCriterion =
+ (TunnelIdCriterion) criterion;
+ return root.put(CriterionCodec.TUNNEL_ID, tunnelIdCriterion.tunnelId());
+ }
+ }
+
+ private class FormatDummyType implements CriterionTypeFormatter {
+
+ @Override
+ public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+ checkNotNull(criterion, "Criterion cannot be null");
+
+ return root.put(CriterionCodec.TYPE, criterion.type().toString());
+
+ }
+ }
+
+ /**
+ * Encodes a criterion into a JSON node.
+ *
+ * @return encoded JSON object for the given criterion
+ */
+ public ObjectNode encode() {
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put(CriterionCodec.TYPE, criterion.type().toString());
+
+ CriterionTypeFormatter formatter =
+ checkNotNull(
+ formatMap.get(criterion.type()),
+ "No formatter found for criterion type "
+ + criterion.type().toString());
+
+ return formatter.encodeCriterion(result, criterion);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
new file mode 100644
index 00000000..d61cf38b
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
@@ -0,0 +1,243 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+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.flow.instructions.L4ModificationInstruction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * JSON encoding of Instructions.
+ */
+public final class EncodeInstructionCodecHelper {
+ protected static final Logger log = LoggerFactory.getLogger(EncodeInstructionCodecHelper.class);
+ private final Instruction instruction;
+ private final CodecContext context;
+
+ /**
+ * Creates an instruction object encoder.
+ *
+ * @param instruction instruction to encode
+ * @param context codec context for the encoding
+ */
+ public EncodeInstructionCodecHelper(Instruction instruction, CodecContext context) {
+ this.instruction = instruction;
+ this.context = context;
+ }
+
+
+ /**
+ * Encode an L0 modification instruction.
+ *
+ * @param result json node that the instruction attributes are added to
+ */
+ private void encodeL0(ObjectNode result) {
+ L0ModificationInstruction instruction =
+ (L0ModificationInstruction) this.instruction;
+ result.put(InstructionCodec.SUBTYPE, instruction.subtype().name());
+
+ switch (instruction.subtype()) {
+ case LAMBDA:
+ final L0ModificationInstruction.ModLambdaInstruction modLambdaInstruction =
+ (L0ModificationInstruction.ModLambdaInstruction) instruction;
+ result.put(InstructionCodec.LAMBDA, modLambdaInstruction.lambda());
+ break;
+
+ case OCH:
+ L0ModificationInstruction.ModOchSignalInstruction ochSignalInstruction =
+ (L0ModificationInstruction.ModOchSignalInstruction) instruction;
+ OchSignal ochSignal = ochSignalInstruction.lambda();
+ result.put(InstructionCodec.GRID_TYPE, ochSignal.gridType().name());
+ result.put(InstructionCodec.CHANNEL_SPACING, ochSignal.channelSpacing().name());
+ result.put(InstructionCodec.SPACING_MULTIPLIER, ochSignal.spacingMultiplier());
+ result.put(InstructionCodec.SLOT_GRANULARITY, ochSignal.slotGranularity());
+ break;
+
+ default:
+ log.info("Cannot convert L0 subtype of {}", instruction.subtype());
+ }
+ }
+
+ /**
+ * Encode an L2 modification instruction.
+ *
+ * @param result json node that the instruction attributes are added to
+ */
+ private void encodeL2(ObjectNode result) {
+ L2ModificationInstruction instruction =
+ (L2ModificationInstruction) this.instruction;
+ result.put(InstructionCodec.SUBTYPE, instruction.subtype().name());
+
+ switch (instruction.subtype()) {
+ case ETH_SRC:
+ case ETH_DST:
+ final L2ModificationInstruction.ModEtherInstruction modEtherInstruction =
+ (L2ModificationInstruction.ModEtherInstruction) instruction;
+ result.put(InstructionCodec.MAC, modEtherInstruction.mac().toString());
+ break;
+
+ case VLAN_ID:
+ final L2ModificationInstruction.ModVlanIdInstruction modVlanIdInstruction =
+ (L2ModificationInstruction.ModVlanIdInstruction) instruction;
+ result.put(InstructionCodec.VLAN_ID, modVlanIdInstruction.vlanId().toShort());
+ break;
+
+ case VLAN_PCP:
+ final L2ModificationInstruction.ModVlanPcpInstruction modVlanPcpInstruction =
+ (L2ModificationInstruction.ModVlanPcpInstruction) instruction;
+ result.put(InstructionCodec.VLAN_PCP, modVlanPcpInstruction.vlanPcp());
+ break;
+
+ case MPLS_LABEL:
+ final L2ModificationInstruction.ModMplsLabelInstruction modMplsLabelInstruction =
+ (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
+ result.put(InstructionCodec.MPLS_LABEL, modMplsLabelInstruction.mplsLabel().toInt());
+ break;
+
+ case MPLS_PUSH:
+ final L2ModificationInstruction.PushHeaderInstructions pushHeaderInstructions =
+ (L2ModificationInstruction.PushHeaderInstructions) instruction;
+
+ result.put(InstructionCodec.ETHERNET_TYPE,
+ pushHeaderInstructions.ethernetType().toShort());
+ break;
+
+ case TUNNEL_ID:
+ final L2ModificationInstruction.ModTunnelIdInstruction modTunnelIdInstruction =
+ (L2ModificationInstruction.ModTunnelIdInstruction) instruction;
+ result.put(InstructionCodec.TUNNEL_ID, modTunnelIdInstruction.tunnelId());
+ break;
+
+ default:
+ log.info("Cannot convert L2 subtype of {}", instruction.subtype());
+ break;
+ }
+ }
+
+ /**
+ * Encode an L3 modification instruction.
+ *
+ * @param result json node that the instruction attributes are added to
+ */
+ private void encodeL3(ObjectNode result) {
+ L3ModificationInstruction instruction =
+ (L3ModificationInstruction) this.instruction;
+ result.put(InstructionCodec.SUBTYPE, instruction.subtype().name());
+ switch (instruction.subtype()) {
+ case IPV4_SRC:
+ case IPV4_DST:
+ case IPV6_SRC:
+ case IPV6_DST:
+ final L3ModificationInstruction.ModIPInstruction modIPInstruction =
+ (L3ModificationInstruction.ModIPInstruction) instruction;
+ result.put(InstructionCodec.IP, modIPInstruction.ip().toString());
+ break;
+
+ case IPV6_FLABEL:
+ final L3ModificationInstruction.ModIPv6FlowLabelInstruction
+ modFlowLabelInstruction =
+ (L3ModificationInstruction.ModIPv6FlowLabelInstruction) instruction;
+ result.put(InstructionCodec.FLOW_LABEL, modFlowLabelInstruction.flowLabel());
+ break;
+
+ default:
+ log.info("Cannot convert L3 subtype of {}", instruction.subtype());
+ break;
+ }
+ }
+
+ /**
+ * Encode a L4 modification instruction.
+ *
+ * @param result json node that the instruction attributes are added to
+ */
+ private void encodeL4(ObjectNode result) {
+ L4ModificationInstruction instruction =
+ (L4ModificationInstruction) this.instruction;
+ result.put(InstructionCodec.SUBTYPE, instruction.subtype().name());
+ switch (instruction.subtype()) {
+ case TCP_DST:
+ case TCP_SRC:
+ final L4ModificationInstruction.ModTransportPortInstruction modTcpPortInstruction =
+ (L4ModificationInstruction.ModTransportPortInstruction) instruction;
+ result.put(InstructionCodec.TCP_PORT, modTcpPortInstruction.port().toInt());
+ break;
+
+ case UDP_DST:
+ case UDP_SRC:
+ final L4ModificationInstruction.ModTransportPortInstruction modUdpPortInstruction =
+ (L4ModificationInstruction.ModTransportPortInstruction) instruction;
+ result.put(InstructionCodec.UDP_PORT, modUdpPortInstruction.port().toInt());
+ break;
+
+ default:
+ log.info("Cannot convert L4 subtype of {}", instruction.subtype());
+ break;
+ }
+ }
+
+ /**
+ * Encodes the given instruction into JSON.
+ *
+ * @return JSON object node representing the instruction
+ */
+ public ObjectNode encode() {
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put(InstructionCodec.TYPE, instruction.type().toString());
+
+ switch (instruction.type()) {
+ case OUTPUT:
+ final Instructions.OutputInstruction outputInstruction =
+ (Instructions.OutputInstruction) instruction;
+ result.put(InstructionCodec.PORT, outputInstruction.port().toLong());
+ break;
+
+ case DROP:
+ break;
+
+ case L0MODIFICATION:
+ encodeL0(result);
+ break;
+
+ case L2MODIFICATION:
+ encodeL2(result);
+ break;
+
+ case L3MODIFICATION:
+ encodeL3(result);
+ break;
+
+ case L4MODIFICATION:
+ encodeL4(result);
+ break;
+
+ default:
+ log.info("Cannot convert instruction type of {}", instruction.type());
+ break;
+ }
+ return result;
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java
new file mode 100644
index 00000000..f56bca46
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.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.codec.impl;
+
+import org.onlab.packet.Ethernet;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Ethernet codec.
+ */
+public final class EthernetCodec extends JsonCodec<Ethernet> {
+
+ protected static final Logger log = LoggerFactory.getLogger(CriterionCodec.class);
+
+ @Override
+ public ObjectNode encode(Ethernet ethernet, CodecContext context) {
+ checkNotNull(ethernet, "Ethernet cannot be null");
+
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put("vlanId", ethernet.getVlanID())
+ .put("etherType", ethernet.getEtherType())
+ .put("priorityCode", ethernet.getPriorityCode())
+ .put("pad", ethernet.isPad());
+
+ if (ethernet.getDestinationMAC() != null) {
+ result.put("destMac",
+ ethernet.getDestinationMAC().toString());
+ }
+
+ if (ethernet.getSourceMAC() != null) {
+ result.put("srcMac",
+ ethernet.getSourceMAC().toString());
+ }
+
+ return result;
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java
new file mode 100644
index 00000000..923bdf2b
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java
@@ -0,0 +1,70 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Flow entry JSON codec.
+ */
+public final class FlowEntryCodec extends JsonCodec<FlowEntry> {
+
+ @Override
+ public ObjectNode encode(FlowEntry flowEntry, CodecContext context) {
+ checkNotNull(flowEntry, "Flow entry cannot be null");
+
+ CoreService service = context.getService(CoreService.class);
+
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put("id", Long.toString(flowEntry.id().value()))
+ .put("appId", service.getAppId(flowEntry.appId()).name())
+ .put("groupId", flowEntry.groupId().id())
+ .put("priority", flowEntry.priority())
+ .put("timeout", flowEntry.timeout())
+ .put("isPermanent", flowEntry.isPermanent())
+ .put("deviceId", flowEntry.deviceId().toString())
+ .put("state", flowEntry.state().toString())
+ .put("life", flowEntry.life())
+ .put("packets", flowEntry.packets())
+ .put("bytes", flowEntry.bytes())
+ .put("lastSeen", flowEntry.lastSeen());
+
+ if (flowEntry.treatment() != null) {
+ final JsonCodec<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ result.set("treatment", treatmentCodec.encode(flowEntry.treatment(), context));
+ }
+
+ if (flowEntry.selector() != null) {
+ final JsonCodec<TrafficSelector> selectorCodec =
+ context.codec(TrafficSelector.class);
+ result.set("selector", selectorCodec.encode(flowEntry.selector(), context));
+ }
+
+ return result;
+ }
+
+}
+
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
new file mode 100644
index 00000000..6c02841e
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
@@ -0,0 +1,94 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Flow rule JSON codec.
+ */
+public final class FlowRuleCodec extends JsonCodec<FlowRule> {
+
+ private static final String PRIORITY = "priority";
+ private static final String TIMEOUT = "timeout";
+ private static final String IS_PERMANENT = "isPermanent";
+ private static final String DEVICE_ID = "deviceId";
+ private static final String TREATMENT = "treatment";
+ private static final String SELECTOR = "selector";
+ private static final String MISSING_MEMBER_MESSAGE =
+ " member is required in FlowRule";
+ public static final String REST_APP_ID = "org.onosproject.rest";
+
+
+ @Override
+ public FlowRule decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ FlowRule.Builder resultBuilder = new DefaultFlowRule.Builder();
+
+ CoreService coreService = context.getService(CoreService.class);
+ resultBuilder.fromApp(coreService
+ .registerApplication(REST_APP_ID));
+
+ int priority = nullIsIllegal(json.get(PRIORITY),
+ PRIORITY + MISSING_MEMBER_MESSAGE).asInt();
+ resultBuilder.withPriority(priority);
+
+ boolean isPermanent = nullIsIllegal(json.get(IS_PERMANENT),
+ IS_PERMANENT + MISSING_MEMBER_MESSAGE).asBoolean();
+ if (isPermanent) {
+ resultBuilder.makePermanent();
+ } else {
+ resultBuilder.makeTemporary(nullIsIllegal(json.get(TIMEOUT),
+ TIMEOUT
+ + MISSING_MEMBER_MESSAGE
+ + " if the flow is temporary").asInt());
+ }
+
+ DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID),
+ DEVICE_ID + MISSING_MEMBER_MESSAGE).asText());
+ resultBuilder.forDevice(deviceId);
+
+ ObjectNode treatmentJson = get(json, TREATMENT);
+ if (treatmentJson != null) {
+ JsonCodec<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ resultBuilder.withTreatment(treatmentCodec.decode(treatmentJson, context));
+ }
+
+ ObjectNode selectorJson = get(json, SELECTOR);
+ if (selectorJson != null) {
+ JsonCodec<TrafficSelector> selectorCodec =
+ context.codec(TrafficSelector.class);
+ resultBuilder.withSelector(selectorCodec.decode(selectorJson, context));
+ }
+
+ return resultBuilder.build();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
new file mode 100644
index 00000000..c710514f
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
@@ -0,0 +1,64 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Group bucket JSON codec.
+ */
+public class GroupBucketCodec extends JsonCodec<GroupBucket> {
+
+ private static final String TYPE = "type";
+ private static final String TREATMENT = "treatment";
+ private static final String WEIGHT = "weight";
+ private static final String WATCH_PORT = "watchPort";
+ private static final String WATCH_GROUP = "watchGroup";
+ private static final String PACKETS = "packets";
+ private static final String BYTES = "bytes";
+
+ @Override
+ public ObjectNode encode(GroupBucket bucket, CodecContext context) {
+ checkNotNull(bucket, "Driver cannot be null");
+
+ ObjectNode result = context.mapper().createObjectNode()
+ .put(TYPE, bucket.type().toString())
+ .put(WEIGHT, bucket.weight())
+ .put(PACKETS, bucket.packets())
+ .put(BYTES, bucket.bytes());
+
+ if (bucket.watchPort() != null) {
+ result.put(WATCH_PORT, bucket.watchPort().toString());
+ }
+
+ if (bucket.watchGroup() != null) {
+ result.put(WATCH_GROUP, bucket.watchGroup().toString());
+ }
+
+ if (bucket.treatment() != null) {
+ result.set(TREATMENT, context.codec(TrafficTreatment.class).encode(bucket.treatment(), context));
+ }
+
+ return result;
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
new file mode 100644
index 00000000..a2f33cee
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
@@ -0,0 +1,79 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Group JSON codec.
+ */
+public final class GroupCodec extends JsonCodec<Group> {
+ // JSON field names
+ private static final String ID = "id";
+ private static final String STATE = "state";
+ private static final String LIFE = "life";
+ private static final String PACKETS = "packets";
+ private static final String BYTES = "bytes";
+ private static final String REFERENCE_COUNT = "referenceCount";
+ private static final String TYPE = "type";
+ private static final String DEVICE_ID = "deviceId";
+ private static final String APP_ID = "appId";
+ private static final String APP_COOKIE = "appCookie";
+ private static final String GIVEN_GROUP_ID = "givenGroupId";
+ private static final String BUCKETS = "buckets";
+
+ @Override
+ public ObjectNode encode(Group group, CodecContext context) {
+ checkNotNull(group, "Group cannot be null");
+ ObjectNode result = context.mapper().createObjectNode()
+ .put(ID, group.id().toString())
+ .put(STATE, group.state().toString())
+ .put(LIFE, group.life())
+ .put(PACKETS, group.packets())
+ .put(BYTES, group.bytes())
+ .put(REFERENCE_COUNT, group.referenceCount())
+ .put(TYPE, group.type().toString())
+ .put(DEVICE_ID, group.deviceId().toString());
+
+ if (group.appId() != null) {
+ result.put(APP_ID, group.appId().toString());
+ }
+
+ if (group.appCookie() != null) {
+ result.put(APP_COOKIE, group.appCookie().toString());
+ }
+
+ if (group.givenGroupId() != null) {
+ result.put(GIVEN_GROUP_ID, group.givenGroupId());
+ }
+
+ ArrayNode buckets = context.mapper().createArrayNode();
+ group.buckets().buckets().forEach(bucket -> {
+ ObjectNode bucketJson = context.codec(GroupBucket.class).encode(bucket, context);
+ buckets.add(bucketJson);
+ });
+ result.set(BUCKETS, buckets);
+ return result;
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java
new file mode 100644
index 00000000..a2402728
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java
@@ -0,0 +1,55 @@
+/*
+ * 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.codec.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostLocation;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Host JSON codec.
+ */
+public final class HostCodec extends AnnotatedCodec<Host> {
+
+ @Override
+ public ObjectNode encode(Host host, CodecContext context) {
+ checkNotNull(host, "Host cannot be null");
+ final JsonCodec<HostLocation> locationCodec =
+ context.codec(HostLocation.class);
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put("id", host.id().toString())
+ .put("mac", host.mac().toString())
+ .put("vlan", host.vlan().toString());
+
+ final ArrayNode jsonIpAddresses = result.putArray("ipAddresses");
+ for (final IpAddress ipAddress : host.ipAddresses()) {
+ jsonIpAddresses.add(ipAddress.toString());
+ }
+ result.set("ipAddresses", jsonIpAddresses);
+ result.set("location", locationCodec.encode(host.location(), context));
+
+ return annotate(result, host, context);
+ }
+
+}
+
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java
new file mode 100644
index 00000000..f8f616d0
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java
@@ -0,0 +1,39 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.HostLocation;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Host JSON codec.
+ */
+public final class HostLocationCodec extends JsonCodec<HostLocation> {
+
+ @Override
+ public ObjectNode encode(HostLocation hostLocation, CodecContext context) {
+ checkNotNull(hostLocation, "Host location cannot be null");
+ return context.mapper().createObjectNode()
+ .put("elementId", hostLocation.elementId().toString())
+ .put("port", hostLocation.port().toString());
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java
new file mode 100644
index 00000000..597ab55c
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.HostId;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.HostToHostIntent;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Host to host intent codec.
+ */
+public final class HostToHostIntentCodec extends JsonCodec<HostToHostIntent> {
+
+ private static final String ONE = "one";
+ private static final String TWO = "two";
+
+ @Override
+ public ObjectNode encode(HostToHostIntent intent, CodecContext context) {
+ checkNotNull(intent, "Host to host intent cannot be null");
+
+ final JsonCodec<ConnectivityIntent> connectivityIntentCodec =
+ context.codec(ConnectivityIntent.class);
+ final ObjectNode result = connectivityIntentCodec.encode(intent, context);
+
+ final String one = intent.one().toString();
+ final String two = intent.two().toString();
+ result.put(ONE, one);
+ result.put(TWO, two);
+
+ return result;
+ }
+
+ @Override
+ public HostToHostIntent decode(ObjectNode json, CodecContext context) {
+ HostToHostIntent.Builder builder = HostToHostIntent.builder();
+
+ IntentCodec.intentAttributes(json, context, builder);
+ ConnectivityIntentCodec.intentAttributes(json, context, builder);
+
+ String one = nullIsIllegal(json.get(ONE),
+ ONE + IntentCodec.MISSING_MEMBER_MESSAGE).asText();
+ builder.one(HostId.hostId(one));
+
+ String two = nullIsIllegal(json.get(TWO),
+ TWO + IntentCodec.MISSING_MEMBER_MESSAGE).asText();
+ builder.two(HostId.hostId(two));
+
+ return builder.build();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
new file mode 100644
index 00000000..f4d5008a
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Instruction codec.
+ */
+public final class InstructionCodec extends JsonCodec<Instruction> {
+
+ protected static final Logger log = LoggerFactory.getLogger(InstructionCodec.class);
+
+ protected static final String TYPE = "type";
+ protected static final String SUBTYPE = "subtype";
+ protected static final String PORT = "port";
+ protected static final String MAC = "mac";
+ protected static final String VLAN_ID = "vlanId";
+ protected static final String VLAN_PCP = "vlanPcp";
+ protected static final String MPLS_LABEL = "label";
+ protected static final String IP = "ip";
+ protected static final String FLOW_LABEL = "flowLabel";
+ protected static final String LAMBDA = "lambda";
+ protected static final String GRID_TYPE = "gridType";
+ protected static final String CHANNEL_SPACING = "channelSpacing";
+ protected static final String SPACING_MULTIPLIER = "spacingMultiplier";
+ protected static final String SLOT_GRANULARITY = "slotGranularity";
+ protected static final String ETHERNET_TYPE = "ethernetType";
+ protected static final String TUNNEL_ID = "tunnelId";
+ protected static final String TCP_PORT = "tcpPort";
+ protected static final String UDP_PORT = "udpPort";
+
+ protected static final String MISSING_MEMBER_MESSAGE =
+ " member is required in Instruction";
+
+
+ @Override
+ public ObjectNode encode(Instruction instruction, CodecContext context) {
+ checkNotNull(instruction, "Instruction cannot be null");
+
+ return new EncodeInstructionCodecHelper(instruction, context).encode();
+ }
+
+ @Override
+ public Instruction decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ return new DecodeInstructionCodecHelper(json).decode();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java
new file mode 100644
index 00000000..8613a964
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java
@@ -0,0 +1,112 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.PointToPointIntent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.net.UrlEscapers;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Intent JSON codec.
+ */
+public final class IntentCodec extends JsonCodec<Intent> {
+
+ protected static final String TYPE = "type";
+ protected static final String ID = "id";
+ protected static final String APP_ID = "appId";
+ protected static final String STATE = "state";
+ protected static final String PRIORITY = "priority";
+ protected static final String RESOURCES = "resources";
+ protected static final String MISSING_MEMBER_MESSAGE =
+ " member is required in Intent";
+
+ @Override
+ public ObjectNode encode(Intent intent, CodecContext context) {
+ checkNotNull(intent, "Intent cannot be null");
+
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put(TYPE, intent.getClass().getSimpleName())
+ .put(ID, intent.id().toString())
+ .put(APP_ID, UrlEscapers.urlPathSegmentEscaper()
+ .escape(intent.appId().name()));
+
+ final ArrayNode jsonResources = result.putArray(RESOURCES);
+
+ for (final NetworkResource resource : intent.resources()) {
+ jsonResources.add(resource.toString());
+ }
+
+ IntentService service = context.getService(IntentService.class);
+ IntentState state = service.getIntentState(intent.key());
+ if (state != null) {
+ result.put(STATE, state.toString());
+ }
+
+ return result;
+ }
+
+ @Override
+ public Intent decode(ObjectNode json, CodecContext context) {
+ checkNotNull(json, "JSON cannot be null");
+
+ String type = nullIsIllegal(json.get(TYPE),
+ TYPE + MISSING_MEMBER_MESSAGE).asText();
+
+ if (type.equals(PointToPointIntent.class.getSimpleName())) {
+ return context.codec(PointToPointIntent.class).decode(json, context);
+ } else if (type.equals(HostToHostIntent.class.getSimpleName())) {
+ return context.codec(HostToHostIntent.class).decode(json, context);
+ }
+
+ throw new IllegalArgumentException("Intent type "
+ + type + " is not supported");
+ }
+
+ /**
+ * Extracts base intent specific attributes from a JSON object
+ * and adds them to a builder.
+ *
+ * @param json root JSON object
+ * @param context code context
+ * @param builder builder to use for storing the attributes
+ */
+ public static void intentAttributes(ObjectNode json, CodecContext context,
+ Intent.Builder builder) {
+ String appId = nullIsIllegal(json.get(IntentCodec.APP_ID),
+ IntentCodec.APP_ID + IntentCodec.MISSING_MEMBER_MESSAGE).asText();
+ CoreService service = context.getService(CoreService.class);
+ builder.appId(service.getAppId(appId));
+
+ JsonNode priorityJson = json.get(IntentCodec.PRIORITY);
+ if (priorityJson != null) {
+ builder.priority(priorityJson.asInt());
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java
new file mode 100644
index 00000000..14ee9b7c
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java
@@ -0,0 +1,80 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.provider.ProviderId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Link JSON codec.
+ */
+public final class LinkCodec extends AnnotatedCodec<Link> {
+
+ // JSON field names
+ private static final String SRC = "src";
+ private static final String DST = "dst";
+ private static final String TYPE = "type";
+ private static final String STATE = "state";
+
+ @Override
+ public ObjectNode encode(Link link, CodecContext context) {
+ checkNotNull(link, "Link cannot be null");
+ JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
+ ObjectNode result = context.mapper().createObjectNode();
+ result.set(SRC, codec.encode(link.src(), context));
+ result.set(DST, codec.encode(link.dst(), context));
+ result.put(TYPE, link.type().toString());
+ if (link.state() != null) {
+ result.put(STATE, link.state().toString());
+ }
+ return annotate(result, link, context);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Note: ProviderId is not part of JSON representation.
+ * Returned object will have random ProviderId set.
+ */
+ @Override
+ public Link decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
+ // TODO: add providerId to JSON if we need to recover them.
+ ProviderId pid = new ProviderId("json", "LinkCodec");
+
+ ConnectPoint src = codec.decode(get(json, SRC), context);
+ ConnectPoint dst = codec.decode(get(json, DST), context);
+ Type type = Type.valueOf(json.get(TYPE).asText());
+ Annotations annotations = extractAnnotations(json, context);
+
+ return new DefaultLink(pid, src, dst, type, annotations);
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java
new file mode 100644
index 00000000..0e55592d
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.statistic.Load;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Codec for the Load class.
+ */
+public class LoadCodec extends JsonCodec<Load> {
+
+ private static final String RATE = "rate";
+ private static final String LATEST = "latest";
+ private static final String VALID = "valid";
+ private static final String TIME = "time";
+
+ @Override
+ public ObjectNode encode(Load load, CodecContext context) {
+ checkNotNull(load, "Load cannot be null");
+ return context.mapper().createObjectNode()
+ .put(RATE, load.rate())
+ .put(LATEST, load.latest())
+ .put(VALID, load.isValid())
+ .put(TIME, load.time());
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java
new file mode 100644
index 00000000..58b48529
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java
@@ -0,0 +1,47 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Path JSON codec.
+ */
+public final class PathCodec extends AnnotatedCodec<Path> {
+ @Override
+ public ObjectNode encode(Path path, CodecContext context) {
+ checkNotNull(path, "Path cannot be null");
+ JsonCodec<Link> codec = context.codec(Link.class);
+ ObjectNode result = context.mapper()
+ .createObjectNode()
+ .put("cost", path.cost());
+ ArrayNode jsonLinks = result.putArray("links");
+
+ for (Link link : path.links()) {
+ jsonLinks.add(codec.encode(link, context));
+ }
+
+ return annotate(result, path, context);
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java
new file mode 100644
index 00000000..20df4890
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java
@@ -0,0 +1,80 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Point to point intent codec.
+ */
+public final class PointToPointIntentCodec extends JsonCodec<PointToPointIntent> {
+
+ private static final String INGRESS_POINT = "ingressPoint";
+ private static final String EGRESS_POINT = "egressPoint";
+
+ @Override
+ public ObjectNode encode(PointToPointIntent intent, CodecContext context) {
+ checkNotNull(intent, "Point to point intent cannot be null");
+
+ final JsonCodec<ConnectivityIntent> connectivityIntentCodec =
+ context.codec(ConnectivityIntent.class);
+ final ObjectNode result = connectivityIntentCodec.encode(intent, context);
+
+ final JsonCodec<ConnectPoint> connectPointCodec =
+ context.codec(ConnectPoint.class);
+ final ObjectNode ingress =
+ connectPointCodec.encode(intent.ingressPoint(), context);
+ final ObjectNode egress =
+ connectPointCodec.encode(intent.egressPoint(), context);
+
+ result.set(INGRESS_POINT, ingress);
+ result.set(EGRESS_POINT, egress);
+
+ return result;
+ }
+
+
+ @Override
+ public PointToPointIntent decode(ObjectNode json, CodecContext context) {
+ PointToPointIntent.Builder builder = PointToPointIntent.builder();
+
+ IntentCodec.intentAttributes(json, context, builder);
+ ConnectivityIntentCodec.intentAttributes(json, context, builder);
+
+ ObjectNode ingressJson = nullIsIllegal(get(json, INGRESS_POINT),
+ INGRESS_POINT + IntentCodec.MISSING_MEMBER_MESSAGE);
+ ConnectPoint ingress = context.codec(ConnectPoint.class)
+ .decode(ingressJson, context);
+ builder.ingressPoint(ingress);
+
+ ObjectNode egressJson = nullIsIllegal(get(json, EGRESS_POINT),
+ EGRESS_POINT + IntentCodec.MISSING_MEMBER_MESSAGE);
+ ConnectPoint egress = context.codec(ConnectPoint.class)
+ .decode(egressJson, context);
+ builder.egressPoint(egress);
+
+ return builder.build();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java
new file mode 100644
index 00000000..c6f2ac76
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java
@@ -0,0 +1,160 @@
+/*
+ * 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onlab.packet.ChassisId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.Port.Type;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Device port JSON codec.
+ */
+public final class PortCodec extends AnnotatedCodec<Port> {
+
+ // JSON field names
+ private static final String ELEMENT = "element"; // DeviceId
+ private static final String PORT_NAME = "port";
+ private static final String IS_ENABLED = "isEnabled";
+ private static final String TYPE = "type";
+ private static final String PORT_SPEED = "portSpeed";
+
+ // Special port name alias
+ private static final String PORT_NAME_LOCAL = "local";
+
+ @Override
+ public ObjectNode encode(Port port, CodecContext context) {
+ checkNotNull(port, "Port cannot be null");
+ ObjectNode result = context.mapper().createObjectNode()
+ .put(ELEMENT, port.element().id().toString())
+ .put(PORT_NAME, portName(port.number()))
+ .put(IS_ENABLED, port.isEnabled())
+ .put(TYPE, port.type().toString().toLowerCase())
+ .put(PORT_SPEED, port.portSpeed());
+ return annotate(result, port, context);
+ }
+
+ private String portName(PortNumber port) {
+ return port.equals(PortNumber.LOCAL) ? PORT_NAME_LOCAL : port.toString();
+ }
+
+ private static PortNumber portNumber(String portName) {
+ if (portName.equalsIgnoreCase(PORT_NAME_LOCAL)) {
+ return PortNumber.LOCAL;
+ }
+
+ return PortNumber.portNumber(portName);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Note: Result of {@link Port#element()} returned Port object,
+ * is not a full Device object.
+ * Only it's DeviceId can be used.
+ */
+ @Override
+ public Port decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ DeviceId did = DeviceId.deviceId(json.get(ELEMENT).asText());
+ Device device = new DummyDevice(did);
+ PortNumber number = portNumber(json.get(PORT_NAME).asText());
+ boolean isEnabled = json.get(IS_ENABLED).asBoolean();
+ Type type = Type.valueOf(json.get(TYPE).asText().toUpperCase());
+ long portSpeed = json.get(PORT_SPEED).asLong();
+ Annotations annotations = extractAnnotations(json, context);
+
+ return new DefaultPort(device, number, isEnabled, type, portSpeed, annotations);
+ }
+
+
+ /**
+ * Dummy Device which only holds DeviceId.
+ */
+ private static final class DummyDevice implements Device {
+
+ private final DeviceId did;
+
+ /**
+ * Constructs Dummy Device which only holds DeviceId.
+ *
+ * @param did device Id
+ */
+ public DummyDevice(DeviceId did) {
+ this.did = did;
+ }
+
+ @Override
+ public Annotations annotations() {
+ return DefaultAnnotations.EMPTY;
+ }
+
+ @Override
+ public ProviderId providerId() {
+ return new ProviderId(did.uri().getScheme(), "PortCodec");
+ }
+
+ @Override
+ public DeviceId id() {
+ return did;
+ }
+
+ @Override
+ public Type type() {
+ return Type.SWITCH;
+ }
+
+ @Override
+ public String manufacturer() {
+ return "dummy";
+ }
+
+ @Override
+ public String hwVersion() {
+ return "0";
+ }
+
+ @Override
+ public String swVersion() {
+ return "0";
+ }
+
+ @Override
+ public String serialNumber() {
+ return "0";
+ }
+
+ @Override
+ public ChassisId chassisId() {
+ return new ChassisId();
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java
new file mode 100644
index 00000000..dc4c79a7
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java
@@ -0,0 +1,41 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.topology.TopologyCluster;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Topology cluster JSON codec.
+ */
+public final class TopologyClusterCodec extends JsonCodec<TopologyCluster> {
+
+ @Override
+ public ObjectNode encode(TopologyCluster cluster, CodecContext context) {
+ checkNotNull(cluster, "Cluster cannot be null");
+
+ return context.mapper().createObjectNode()
+ .put("id", cluster.id().index())
+ .put("deviceCount", cluster.deviceCount())
+ .put("linkCount", cluster.linkCount())
+ .put("root", cluster.root().toString());
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java
new file mode 100644
index 00000000..f6529eb5
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java
@@ -0,0 +1,41 @@
+/*
+ * 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.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.topology.Topology;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Topology JSON codec.
+ */
+public final class TopologyCodec extends JsonCodec<Topology> {
+
+ @Override
+ public ObjectNode encode(Topology topology, CodecContext context) {
+ checkNotNull(topology, "Topology cannot be null");
+
+ return context.mapper().createObjectNode()
+ .put("time", topology.time())
+ .put("devices", topology.deviceCount())
+ .put("links", topology.linkCount())
+ .put("clusters", topology.clusterCount());
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java
new file mode 100644
index 00000000..24ebef1a
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java
@@ -0,0 +1,71 @@
+/*
+ * 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.codec.impl;
+
+import java.util.stream.IntStream;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Traffic selector codec.
+ */
+public final class TrafficSelectorCodec extends JsonCodec<TrafficSelector> {
+ private static final String CRITERIA = "criteria";
+
+ @Override
+ public ObjectNode encode(TrafficSelector selector, CodecContext context) {
+ checkNotNull(selector, "Traffic selector cannot be null");
+
+ final ObjectNode result = context.mapper().createObjectNode();
+ final ArrayNode jsonCriteria = result.putArray(CRITERIA);
+
+ if (selector.criteria() != null) {
+ final JsonCodec<Criterion> criterionCodec =
+ context.codec(Criterion.class);
+ for (final Criterion criterion : selector.criteria()) {
+ jsonCriteria.add(criterionCodec.encode(criterion, context));
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public TrafficSelector decode(ObjectNode json, CodecContext context) {
+ final JsonCodec<Criterion> criterionCodec =
+ context.codec(Criterion.class);
+
+ JsonNode criteriaJson = json.get(CRITERIA);
+ TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
+ if (criteriaJson != null) {
+ IntStream.range(0, criteriaJson.size())
+ .forEach(i -> builder.add(
+ criterionCodec.decode(get(criteriaJson, i),
+ context)));
+ }
+ return builder.build();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java
new file mode 100644
index 00000000..0d7fb420
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.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.codec.impl;
+
+import java.util.stream.IntStream;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Traffic treatment codec.
+ */
+public final class TrafficTreatmentCodec extends JsonCodec<TrafficTreatment> {
+ private static final String INSTRUCTIONS = "instructions";
+
+ @Override
+ public ObjectNode encode(TrafficTreatment treatment, CodecContext context) {
+ checkNotNull(treatment, "Traffic treatment cannot be null");
+
+ final ObjectNode result = context.mapper().createObjectNode();
+ final ArrayNode jsonInstructions = result.putArray(INSTRUCTIONS);
+
+ final JsonCodec<Instruction> instructionCodec =
+ context.codec(Instruction.class);
+
+ for (final Instruction instruction : treatment.immediate()) {
+ jsonInstructions.add(instructionCodec.encode(instruction, context));
+ }
+
+ final ArrayNode jsonDeferred = result.putArray("deferred");
+
+ for (final Instruction instruction : treatment.deferred()) {
+ jsonDeferred.add(instructionCodec.encode(instruction, context));
+ }
+
+ return result;
+ }
+
+ @Override
+ public TrafficTreatment decode(ObjectNode json, CodecContext context) {
+ final JsonCodec<Instruction> instructionsCodec =
+ context.codec(Instruction.class);
+
+ JsonNode instructionsJson = json.get(INSTRUCTIONS);
+ TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
+ if (instructionsJson != null) {
+ IntStream.range(0, instructionsJson.size())
+ .forEach(i -> builder.add(
+ instructionsCodec.decode(get(instructionsJson, i),
+ context)));
+ }
+ return builder.build();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java
new file mode 100644
index 00000000..b3113e97
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implementations of the codec broker and built-in entity JSON codecs.
+ */
+package org.onosproject.codec.impl;
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java
new file mode 100644
index 00000000..bdf7d732
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java
@@ -0,0 +1,502 @@
+/*
+ * 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.common;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSetMultimap.Builder;
+import org.onlab.graph.DijkstraGraphSearch;
+import org.onlab.graph.GraphPathSearch;
+import org.onlab.graph.GraphPathSearch.Result;
+import org.onlab.graph.TarjanGraphSearch;
+import org.onlab.graph.TarjanGraphSearch.SCCResult;
+import org.onosproject.net.AbstractModel;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.ClusterId;
+import org.onosproject.net.topology.DefaultTopologyCluster;
+import org.onosproject.net.topology.DefaultTopologyVertex;
+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.TopologyEdge;
+import org.onosproject.net.topology.TopologyGraph;
+import org.onosproject.net.topology.TopologyVertex;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
+import static org.onlab.util.Tools.isNullOrEmpty;
+import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.State.INACTIVE;
+import static org.onosproject.net.Link.Type.INDIRECT;
+
+/**
+ * Default implementation of the topology descriptor. This carries the backing
+ * topology data.
+ */
+public class DefaultTopology extends AbstractModel implements Topology {
+
+ private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA = new DijkstraGraphSearch<>();
+ private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN = new TarjanGraphSearch<>();
+
+ private final long time;
+ private final long creationTime;
+ private final long computeCost;
+ private final TopologyGraph graph;
+
+ private final LinkWeight weight;
+ private final Supplier<SCCResult<TopologyVertex, TopologyEdge>> clusterResults;
+ private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
+ private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
+ private final Supplier<ImmutableSetMultimap<ClusterId, ConnectPoint>> broadcastSets;
+ private final Function<ConnectPoint, Boolean> broadcastFunction;
+ private final Supplier<ClusterIndexes> clusterIndexes;
+
+ /**
+ * Creates a topology descriptor attributed to the specified provider.
+ *
+ * @param providerId identity of the provider
+ * @param description data describing the new topology
+ * @param broadcastFunction broadcast point function
+ */
+ public DefaultTopology(ProviderId providerId, GraphDescription description,
+ Function<ConnectPoint, Boolean> broadcastFunction) {
+ super(providerId);
+ this.broadcastFunction = broadcastFunction;
+ this.time = description.timestamp();
+ this.creationTime = description.creationTime();
+
+ // Build the graph
+ this.graph = new DefaultTopologyGraph(description.vertexes(),
+ description.edges());
+
+ this.clusterResults = Suppliers.memoize(() -> searchForClusters());
+ this.clusters = Suppliers.memoize(() -> buildTopologyClusters());
+
+ this.clusterIndexes = Suppliers.memoize(() -> buildIndexes());
+
+ this.weight = new HopCountLinkWeight(graph.getVertexes().size());
+ this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
+ this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
+ this.computeCost = Math.max(0, System.nanoTime() - time);
+ }
+
+ /**
+ * Creates a topology descriptor attributed to the specified provider.
+ *
+ * @param providerId identity of the provider
+ * @param description data describing the new topology
+ */
+ public DefaultTopology(ProviderId providerId, GraphDescription description) {
+ this(providerId, description, null);
+ }
+
+ @Override
+ public long time() {
+ return time;
+ }
+
+ @Override
+ public long creationTime() {
+ return creationTime;
+ }
+
+ @Override
+ public long computeCost() {
+ return computeCost;
+ }
+
+ @Override
+ public int clusterCount() {
+ return clusters.get().size();
+ }
+
+ @Override
+ public int deviceCount() {
+ return graph.getVertexes().size();
+ }
+
+ @Override
+ public int linkCount() {
+ return graph.getEdges().size();
+ }
+
+ private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice() {
+ return clusterIndexes.get().clustersByDevice;
+ }
+
+ private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster() {
+ return clusterIndexes.get().devicesByCluster;
+ }
+
+ private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster() {
+ return clusterIndexes.get().linksByCluster;
+ }
+
+ /**
+ * Returns the backing topology graph.
+ *
+ * @return topology graph
+ */
+ public TopologyGraph getGraph() {
+ return graph;
+ }
+
+ /**
+ * Returns the set of topology clusters.
+ *
+ * @return set of clusters
+ */
+ public Set<TopologyCluster> getClusters() {
+ return ImmutableSet.copyOf(clusters.get().values());
+ }
+
+ /**
+ * Returns the specified topology cluster.
+ *
+ * @param clusterId cluster identifier
+ * @return topology cluster
+ */
+ public TopologyCluster getCluster(ClusterId clusterId) {
+ return clusters.get().get(clusterId);
+ }
+
+ /**
+ * Returns the topology cluster that contains the given device.
+ *
+ * @param deviceId device identifier
+ * @return topology cluster
+ */
+ public TopologyCluster getCluster(DeviceId deviceId) {
+ return clustersByDevice().get(deviceId);
+ }
+
+ /**
+ * Returns the set of cluster devices.
+ *
+ * @param cluster topology cluster
+ * @return cluster devices
+ */
+ public Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
+ return devicesByCluster().get(cluster);
+ }
+
+ /**
+ * Returns the set of cluster links.
+ *
+ * @param cluster topology cluster
+ * @return cluster links
+ */
+ public Set<Link> getClusterLinks(TopologyCluster cluster) {
+ return linksByCluster().get(cluster);
+ }
+
+ /**
+ * Indicates whether the given point is an infrastructure link end-point.
+ *
+ * @param connectPoint connection point
+ * @return true if infrastructure
+ */
+ public boolean isInfrastructure(ConnectPoint connectPoint) {
+ return infrastructurePoints.get().contains(connectPoint);
+ }
+
+ /**
+ * Indicates whether the given point is part of a broadcast set.
+ *
+ * @param connectPoint connection point
+ * @return true if in broadcast set
+ */
+ public boolean isBroadcastPoint(ConnectPoint connectPoint) {
+ if (broadcastFunction != null) {
+ return broadcastFunction.apply(connectPoint);
+ }
+
+ // Any non-infrastructure, i.e. edge points are assumed to be OK.
+ if (!isInfrastructure(connectPoint)) {
+ return true;
+ }
+
+ // Find the cluster to which the device belongs.
+ TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId());
+ checkArgument(cluster != null, "No cluster found for device %s", connectPoint.deviceId());
+
+ // If the broadcast set is null or empty, or if the point explicitly
+ // belongs to it, return true.
+ Set<ConnectPoint> points = broadcastSets.get().get(cluster.id());
+ return isNullOrEmpty(points) || points.contains(connectPoint);
+ }
+
+ /**
+ * Returns the size of the cluster broadcast set.
+ *
+ * @param clusterId cluster identifier
+ * @return size of the cluster broadcast set
+ */
+ public int broadcastSetSize(ClusterId clusterId) {
+ return broadcastSets.get().get(clusterId).size();
+ }
+
+ /**
+ * Returns the set of the cluster broadcast points.
+ *
+ * @param clusterId cluster identifier
+ * @return set of cluster broadcast points
+ */
+ public Set<ConnectPoint> broadcastPoints(ClusterId clusterId) {
+ return broadcastSets.get().get(clusterId);
+ }
+
+ /**
+ * Returns the set of pre-computed shortest paths between source and
+ * destination devices.
+ *
+ * @param src source device
+ * @param dst destination device
+ * @return set of shortest paths
+ */
+ public Set<Path> getPaths(DeviceId src, DeviceId dst) {
+ return getPaths(src, dst, null);
+ }
+
+ /**
+ * Computes on-demand the set of shortest paths between source and
+ * destination devices.
+ *
+ * @param src source device
+ * @param dst destination device
+ * @param weight link weight function
+ * @return set of shortest paths
+ */
+ public Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
+ final DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
+ final DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
+ Set<TopologyVertex> vertices = graph.getVertexes();
+ if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
+ // src or dst not part of the current graph
+ return ImmutableSet.of();
+ }
+
+ GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
+ DIJKSTRA.search(graph, srcV, dstV, weight, ALL_PATHS);
+ ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
+ for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
+ builder.add(networkPath(path));
+ }
+ return builder.build();
+ }
+
+ // Converts graph path to a network path with the same cost.
+ private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
+ List<Link> links = new ArrayList<>();
+ for (TopologyEdge edge : path.edges()) {
+ links.add(edge.link());
+ }
+ return new DefaultPath(CORE_PROVIDER_ID, links, path.cost());
+ }
+
+ // Searches for SCC clusters in the network topology graph using Tarjan
+ // algorithm.
+ private SCCResult<TopologyVertex, TopologyEdge> searchForClusters() {
+ return TARJAN.search(graph, new NoIndirectLinksWeight());
+ }
+
+ // Builds the topology clusters and returns the id-cluster bindings.
+ private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
+ ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
+ SCCResult<TopologyVertex, TopologyEdge> results = clusterResults.get();
+ // Extract both vertexes and edges from the results; the lists form
+ // pairs along the same index.
+ List<Set<TopologyVertex>> clusterVertexes = results.clusterVertexes();
+ List<Set<TopologyEdge>> clusterEdges = results.clusterEdges();
+
+ // Scan over the lists and create a cluster from the results.
+ for (int i = 0, n = results.clusterCount(); i < n; i++) {
+ Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
+ Set<TopologyEdge> edgeSet = clusterEdges.get(i);
+
+ ClusterId cid = ClusterId.clusterId(i);
+ DefaultTopologyCluster cluster = new DefaultTopologyCluster(cid,
+ vertexSet.size(),
+ edgeSet.size(),
+ findRoot(vertexSet));
+ clusterBuilder.put(cid, cluster);
+ }
+ return clusterBuilder.build();
+ }
+
+ // Finds the vertex whose device id is the lexicographical minimum in the
+ // specified set.
+ private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
+ TopologyVertex minVertex = null;
+ for (TopologyVertex vertex : vertexSet) {
+ if ((minVertex == null) || (minVertex.deviceId()
+ .toString().compareTo(minVertex.deviceId().toString()) < 0)) {
+ minVertex = vertex;
+ }
+ }
+ return minVertex;
+ }
+
+ // Processes a map of broadcast sets for each cluster.
+ private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
+ Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap
+ .builder();
+ for (TopologyCluster cluster : clusters.get().values()) {
+ addClusterBroadcastSet(cluster, builder);
+ }
+ return builder.build();
+ }
+
+ // Finds all broadcast points for the cluster. These are those connection
+ // points which lie along the shortest paths between the cluster root and
+ // all other devices within the cluster.
+ private void addClusterBroadcastSet(TopologyCluster cluster, Builder<ClusterId, ConnectPoint> builder) {
+ // Use the graph root search results to build the broadcast set.
+ Result<TopologyVertex, TopologyEdge> result = DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
+ for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
+ TopologyVertex vertex = entry.getKey();
+
+ // Ignore any parents that lead outside the cluster.
+ if (clustersByDevice().get(vertex.deviceId()) != cluster) {
+ continue;
+ }
+
+ // Ignore any back-link sets that are empty.
+ Set<TopologyEdge> parents = entry.getValue();
+ if (parents.isEmpty()) {
+ continue;
+ }
+
+ // Use the first back-link source and destinations to add to the
+ // broadcast set.
+ Link link = parents.iterator().next().link();
+ builder.put(cluster.id(), link.src());
+ builder.put(cluster.id(), link.dst());
+ }
+ }
+
+ // Collects and returns an set of all infrastructure link end-points.
+ private ImmutableSet<ConnectPoint> findInfrastructurePoints() {
+ ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
+ for (TopologyEdge edge : graph.getEdges()) {
+ builder.add(edge.link().src());
+ builder.add(edge.link().dst());
+ }
+ return builder.build();
+ }
+
+ // Builds cluster-devices, cluster-links and device-cluster indexes.
+ private ClusterIndexes buildIndexes() {
+ // Prepare the index builders
+ ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder =
+ ImmutableMap.builder();
+ ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder =
+ ImmutableSetMultimap.builder();
+ ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder =
+ ImmutableSetMultimap.builder();
+
+ // Now scan through all the clusters
+ for (TopologyCluster cluster : clusters.get().values()) {
+ int i = cluster.id().index();
+
+ // Scan through all the cluster vertexes.
+ for (TopologyVertex vertex : clusterResults.get().clusterVertexes().get(i)) {
+ devicesBuilder.put(cluster, vertex.deviceId());
+ clusterBuilder.put(vertex.deviceId(), cluster);
+ }
+
+ // Scan through all the cluster edges.
+ for (TopologyEdge edge : clusterResults.get().clusterEdges().get(i)) {
+ linksBuilder.put(cluster, edge.link());
+ }
+ }
+
+ // Finalize all indexes.
+ return new ClusterIndexes(clusterBuilder.build(),
+ devicesBuilder.build(),
+ linksBuilder.build());
+ }
+
+ // Link weight for measuring link cost as hop count with indirect links
+ // being as expensive as traversing the entire graph to assume the worst.
+ private static class HopCountLinkWeight implements LinkWeight {
+ private final int indirectLinkCost;
+
+ HopCountLinkWeight(int indirectLinkCost) {
+ this.indirectLinkCost = indirectLinkCost;
+ }
+
+ @Override
+ public double weight(TopologyEdge edge) {
+ // To force preference to use direct paths first, make indirect
+ // links as expensive as the linear vertex traversal.
+ return edge.link().state() ==
+ ACTIVE ? (edge.link().type() ==
+ INDIRECT ? indirectLinkCost : 1) : -1;
+ }
+ }
+
+ // Link weight for preventing traversal over indirect links.
+ private static class NoIndirectLinksWeight implements LinkWeight {
+ @Override
+ public double weight(TopologyEdge edge) {
+ return (edge.link().state() == INACTIVE)
+ || (edge.link().type() == INDIRECT) ? -1 : 1;
+ }
+ }
+
+ static final class ClusterIndexes {
+ final ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
+ final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
+ final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
+
+ public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
+ ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
+ ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
+ this.clustersByDevice = clustersByDevice;
+ this.devicesByCluster = devicesByCluster;
+ this.linksByCluster = linksByCluster;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("time", time)
+ .add("creationTime", creationTime)
+ .add("computeCost", computeCost)
+ .add("clusters", clusterCount())
+ .add("devices", deviceCount())
+ .add("links", linkCount()).toString();
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java
new file mode 100644
index 00000000..b06065e7
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java
@@ -0,0 +1,43 @@
+/*
+ * 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.common;
+
+import org.onlab.graph.AdjacencyListsGraph;
+import org.onosproject.net.topology.TopologyEdge;
+import org.onosproject.net.topology.TopologyGraph;
+import org.onosproject.net.topology.TopologyVertex;
+
+import java.util.Set;
+
+/**
+ * Default implementation of an immutable topology graph based on a generic
+ * implementation of adjacency lists graph.
+ */
+public class DefaultTopologyGraph
+ extends AdjacencyListsGraph<TopologyVertex, TopologyEdge>
+ implements TopologyGraph {
+
+ /**
+ * Creates a topology graph comprising of the specified vertexes and edges.
+ *
+ * @param vertexes set of graph vertexes
+ * @param edges set of graph edges
+ */
+ public DefaultTopologyGraph(Set<TopologyVertex> vertexes, Set<TopologyEdge> edges) {
+ super(vertexes, edges);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
new file mode 100644
index 00000000..54f0fb89
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
@@ -0,0 +1,432 @@
+/*
+ * 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.common.app;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.onlab.util.Tools;
+import org.onosproject.app.ApplicationDescription;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationException;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.app.DefaultApplicationDescription;
+import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.Version;
+import org.onosproject.security.AppPermission;
+import org.onosproject.security.Permission;
+import org.onosproject.store.AbstractStore;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.NoSuchFileException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static com.google.common.io.Files.createParentDirs;
+import static com.google.common.io.Files.write;
+
+/**
+ * Facility for reading application archive stream and managing application
+ * directory structure.
+ */
+public class ApplicationArchive
+ extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> {
+
+ private static Logger log = LoggerFactory.getLogger(ApplicationArchive.class);
+
+ // Magic strings to search for at the beginning of the archive stream
+ private static final String XML_MAGIC = "<?xml ";
+
+ // Magic strings to search for and how deep to search it into the archive stream
+ private static final String APP_MAGIC = "<app ";
+ private static final int APP_MAGIC_DEPTH = 1024;
+
+ private static final String NAME = "[@name]";
+ private static final String ORIGIN = "[@origin]";
+ private static final String VERSION = "[@version]";
+ private static final String FEATURES_REPO = "[@featuresRepo]";
+ private static final String FEATURES = "[@features]";
+ private static final String DESCRIPTION = "description";
+
+ private static final String ROLE = "security.role";
+ private static final String APP_PERMISSIONS = "security.permissions.app-perm";
+ private static final String NET_PERMISSIONS = "security.permissions.net-perm";
+ private static final String JAVA_PERMISSIONS = "security.permissions.java-perm";
+
+ private static final String OAR = ".oar";
+ private static final String APP_XML = "app.xml";
+ private static final String M2_PREFIX = "m2";
+
+ private static final String ROOT = "../";
+ private static final String M2_ROOT = "system/";
+ private static final String APPS_ROOT = "apps/";
+
+ private File root = new File(ROOT);
+ private File appsDir = new File(root, APPS_ROOT);
+ private File m2Dir = new File(M2_ROOT);
+
+ /**
+ * Sets the root directory where apps directory is contained.
+ *
+ * @param root top-level directory path
+ */
+ protected void setRootPath(String root) {
+ this.root = new File(root);
+ this.appsDir = new File(this.root, APPS_ROOT);
+ this.m2Dir = new File(M2_ROOT);
+ }
+
+ /**
+ * Returns the root directory where apps directory is contained.
+ *
+ * @return top-level directory path
+ */
+ public String getRootPath() {
+ return root.getPath();
+ }
+
+ /**
+ * Returns the set of installed application names.
+ *
+ * @return installed application names
+ */
+ public Set<String> getApplicationNames() {
+ ImmutableSet.Builder<String> names = ImmutableSet.builder();
+ File[] files = appsDir.listFiles(File::isDirectory);
+ if (files != null) {
+ for (File file : files) {
+ names.add(file.getName());
+ }
+ }
+ return names.build();
+ }
+
+ /**
+ * Returns the timestamp in millis since start of epoch, of when the
+ * specified application was last modified or changed state.
+ *
+ * @param appName application name
+ * @return number of millis since start of epoch
+ */
+ public long getUpdateTime(String appName) {
+ return appFile(appName, APP_XML).lastModified();
+ }
+
+ /**
+ * Loads the application descriptor from the specified application archive
+ * stream and saves the stream in the appropriate application archive
+ * directory.
+ *
+ * @param appName application name
+ * @return application descriptor
+ * @throws org.onosproject.app.ApplicationException if unable to read application description
+ */
+ public ApplicationDescription getApplicationDescription(String appName) {
+ try {
+ XMLConfiguration cfg = new XMLConfiguration();
+ cfg.setAttributeSplittingDisabled(true);
+ cfg.setDelimiterParsingDisabled(true);
+ cfg.load(appFile(appName, APP_XML));
+ return loadAppDescription(cfg);
+ } catch (Exception e) {
+ throw new ApplicationException("Unable to get app description", e);
+ }
+ }
+
+ /**
+ * Loads the application descriptor from the specified application archive
+ * stream and saves the stream in the appropriate application archive
+ * directory.
+ *
+ * @param stream application archive stream
+ * @return application descriptor
+ * @throws org.onosproject.app.ApplicationException if unable to read the
+ * archive stream or store
+ * the application archive
+ */
+ public synchronized ApplicationDescription saveApplication(InputStream stream) {
+ try (InputStream ais = stream) {
+ byte[] cache = toByteArray(ais);
+ InputStream bis = new ByteArrayInputStream(cache);
+
+ boolean plainXml = isPlainXml(cache);
+ ApplicationDescription desc = plainXml ?
+ parsePlainAppDescription(bis) : parseZippedAppDescription(bis);
+ checkState(!appFile(desc.name(), APP_XML).exists(),
+ "Application %s already installed", desc.name());
+
+ if (plainXml) {
+ expandPlainApplication(cache, desc);
+ } else {
+ bis.reset();
+ expandZippedApplication(bis, desc);
+
+ bis.reset();
+ saveApplication(bis, desc);
+ }
+
+ installArtifacts(desc);
+ return desc;
+ } catch (IOException e) {
+ throw new ApplicationException("Unable to save application", e);
+ }
+ }
+
+ // Indicates whether the stream encoded in the given bytes is plain XML.
+ private boolean isPlainXml(byte[] bytes) {
+ return substring(bytes, XML_MAGIC.length()).equals(XML_MAGIC) ||
+ substring(bytes, APP_MAGIC_DEPTH).contains(APP_MAGIC);
+ }
+
+ // Returns the substring of maximum possible length from the specified bytes.
+ private String substring(byte[] bytes, int length) {
+ return new String(bytes, 0, Math.min(bytes.length, length), Charset.forName("UTF-8"));
+ }
+
+ /**
+ * Purges the application archive directory.
+ *
+ * @param appName application name
+ */
+ public synchronized void purgeApplication(String appName) {
+ File appDir = new File(appsDir, appName);
+ try {
+ Tools.removeDirectory(appDir);
+ } catch (IOException e) {
+ throw new ApplicationException("Unable to purge application " + appName, e);
+ }
+ if (appDir.exists()) {
+ throw new ApplicationException("Unable to purge application " + appName);
+ }
+ }
+
+ /**
+ * Returns application archive stream for the specified application. This
+ * will be either the application ZIP file or the application XML file.
+ *
+ * @param appName application name
+ * @return application archive stream
+ */
+ public synchronized InputStream getApplicationInputStream(String appName) {
+ try {
+ File appFile = appFile(appName, appName + OAR);
+ return new FileInputStream(appFile.exists() ? appFile : appFile(appName, APP_XML));
+ } catch (FileNotFoundException e) {
+ throw new ApplicationException("Application " + appName + " not found");
+ }
+ }
+
+ // Scans the specified ZIP stream for app.xml entry and parses it producing
+ // an application descriptor.
+ private ApplicationDescription parseZippedAppDescription(InputStream stream)
+ throws IOException {
+ try (ZipInputStream zis = new ZipInputStream(stream)) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (entry.getName().equals(APP_XML)) {
+ byte[] data = ByteStreams.toByteArray(zis);
+ return parsePlainAppDescription(new ByteArrayInputStream(data));
+ }
+ zis.closeEntry();
+ }
+ }
+ throw new IOException("Unable to locate " + APP_XML);
+ }
+
+ // Scans the specified XML stream and parses it producing an application descriptor.
+ private ApplicationDescription parsePlainAppDescription(InputStream stream)
+ throws IOException {
+ XMLConfiguration cfg = new XMLConfiguration();
+ cfg.setAttributeSplittingDisabled(true);
+ cfg.setDelimiterParsingDisabled(true);
+ try {
+ cfg.load(stream);
+ return loadAppDescription(cfg);
+ } catch (ConfigurationException e) {
+ throw new IOException("Unable to parse " + APP_XML, e);
+ }
+ }
+
+ private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
+ String name = cfg.getString(NAME);
+ Version version = Version.version(cfg.getString(VERSION));
+ String desc = cfg.getString(DESCRIPTION);
+ String origin = cfg.getString(ORIGIN);
+ ApplicationRole role = getRole(cfg.getString(ROLE));
+ Set<Permission> perms = getPermissions(cfg);
+ String featRepo = cfg.getString(FEATURES_REPO);
+ URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
+ List<String> features = ImmutableList.copyOf(cfg.getString(FEATURES).split(","));
+
+ return new DefaultApplicationDescription(name, version, desc, origin, role,
+ perms, featuresRepo, features);
+ }
+
+ // Expands the specified ZIP stream into app-specific directory.
+ private void expandZippedApplication(InputStream stream, ApplicationDescription desc)
+ throws IOException {
+ ZipInputStream zis = new ZipInputStream(stream);
+ ZipEntry entry;
+ File appDir = new File(appsDir, desc.name());
+ while ((entry = zis.getNextEntry()) != null) {
+ if (!entry.isDirectory()) {
+ byte[] data = ByteStreams.toByteArray(zis);
+ zis.closeEntry();
+ File file = new File(appDir, entry.getName());
+ createParentDirs(file);
+ write(data, file);
+ }
+ }
+ zis.close();
+ }
+
+ // Saves the specified XML stream into app-specific directory.
+ private void expandPlainApplication(byte[] stream, ApplicationDescription desc)
+ throws IOException {
+ File file = appFile(desc.name(), APP_XML);
+ checkState(!file.getParentFile().exists(), "Application already installed");
+ createParentDirs(file);
+ write(stream, file);
+ }
+
+
+ // Saves the specified ZIP stream into a file under app-specific directory.
+ private void saveApplication(InputStream stream, ApplicationDescription desc)
+ throws IOException {
+ Files.write(toByteArray(stream), appFile(desc.name(), desc.name() + OAR));
+ }
+
+ // Installs application artifacts into M2 repository.
+ private void installArtifacts(ApplicationDescription desc) throws IOException {
+ try {
+ Tools.copyDirectory(appFile(desc.name(), M2_PREFIX), m2Dir);
+ } catch (NoSuchFileException e) {
+ log.debug("Application {} has no M2 artifacts", desc.name());
+ }
+ }
+
+ /**
+ * Marks the app as active by creating token file in the app directory.
+ *
+ * @param appName application name
+ * @return true if file was created
+ */
+ protected boolean setActive(String appName) {
+ try {
+ return appFile(appName, "active").createNewFile() && updateTime(appName);
+ } catch (IOException e) {
+ throw new ApplicationException("Unable to mark app as active", e);
+ }
+ }
+
+ /**
+ * Clears the app as active by deleting token file in the app directory.
+ *
+ * @param appName application name
+ * @return true if file was deleted
+ */
+ protected boolean clearActive(String appName) {
+ return appFile(appName, "active").delete() && updateTime(appName);
+ }
+
+ /**
+ * Updates the time-stamp of the app descriptor file.
+ *
+ * @param appName application name
+ * @return true if the app descriptor was updated
+ */
+ protected boolean updateTime(String appName) {
+ return appFile(appName, APP_XML).setLastModified(System.currentTimeMillis());
+ }
+
+ /**
+ * Indicates whether the app was marked as active by checking for token file.
+ *
+ * @param appName application name
+ * @return true if the app is marked as active
+ */
+ protected boolean isActive(String appName) {
+ return appFile(appName, "active").exists();
+ }
+
+
+ // Returns the name of the file located under the specified app directory.
+ private File appFile(String appName, String fileName) {
+ return new File(new File(appsDir, appName), fileName);
+ }
+
+ // Returns the set of Permissions specified in the app.xml file
+ private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) {
+ List<Permission> permissionList = new ArrayList();
+
+ for (Object o : cfg.getList(APP_PERMISSIONS)) {
+ String name = (String) o;
+ permissionList.add(new Permission(AppPermission.class.getName(), name));
+ }
+ for (Object o : cfg.getList(NET_PERMISSIONS)) {
+ //TODO: TO BE FLESHED OUT WHEN NETWORK PERMISSIONS ARE SUPPORTED
+ break;
+ }
+
+ List<HierarchicalConfiguration> fields =
+ cfg.configurationsAt(JAVA_PERMISSIONS);
+ for (HierarchicalConfiguration sub : fields) {
+ String classname = sub.getString("classname");
+ String name = sub.getString("name");
+ String actions = sub.getString("actions");
+
+ if (classname != null && name != null) {
+ permissionList.add(new Permission(classname, name, actions));
+ }
+ }
+ return ImmutableSet.copyOf(permissionList);
+ }
+
+ //
+ // Returns application role type
+ public ApplicationRole getRole(String value) {
+ if (value == null) {
+ return ApplicationRole.UNSPECIFIED;
+ } else {
+ try {
+ return ApplicationRole.valueOf(value.toUpperCase(Locale.ENGLISH));
+ } catch (IllegalArgumentException e) {
+ log.debug("Unknown role value: %s", value);
+ return ApplicationRole.UNSPECIFIED;
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java
new file mode 100644
index 00000000..898bad7b
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/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.
+ */
+
+/**
+ * Common facilities for construction of application management subsystem.
+ */
+package org.onosproject.common.app; \ No newline at end of file
diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java
new file mode 100644
index 00000000..8cd9b8c4
--- /dev/null
+++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Miscellaneous common facilities used for construction of various core and
+ * app subsystems.
+ */
+package org.onosproject.common; \ No newline at end of file
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java
new file mode 100644
index 00000000..8d85751e
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java
@@ -0,0 +1,71 @@
+/*
+ * 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.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.ConnectPoint;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for connect points.
+ */
+
+public final class ConnectPointJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final ConnectPoint connectPoint;
+
+ private ConnectPointJsonMatcher(ConnectPoint connectPointValue) {
+ connectPoint = connectPointValue;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonConnectPoint, Description description) {
+ // check device
+ final String jsonDevice = jsonConnectPoint.get("device").asText();
+ final String device = connectPoint.deviceId().toString();
+ if (!jsonDevice.equals(device)) {
+ description.appendText("device was " + jsonDevice);
+ return false;
+ }
+
+ // check port
+ final String jsonPort = jsonConnectPoint.get("port").asText();
+ final String port = connectPoint.port().toString();
+ if (!jsonPort.equals(port)) {
+ description.appendText("port was " + jsonPort);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(connectPoint.toString());
+ }
+
+ /**
+ * Factory to allocate an connect point matcher.
+ *
+ * @param connectPoint connect point object we are looking for
+ * @return matcher
+ */
+ public static ConnectPointJsonMatcher matchesConnectPoint(ConnectPoint connectPoint) {
+ return new ConnectPointJsonMatcher(connectPoint);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java
new file mode 100644
index 00000000..2a47d115
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.codec.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.AsymmetricPathConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.LinkTypeConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+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.hasItem;
+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.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Unit tests for Constraint codec.
+ */
+public class ConstraintCodecTest {
+
+ MockCodecContext context;
+ JsonCodec<Constraint> constraintCodec;
+ final CoreService mockCoreService = createMock(CoreService.class);
+
+ /**
+ * Sets up for each test. Creates a context and fetches the flow rule
+ * codec.
+ */
+ @Before
+ public void setUp() {
+ context = new MockCodecContext();
+ constraintCodec = context.codec(Constraint.class);
+ assertThat(constraintCodec, notNullValue());
+
+ expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
+ .andReturn(APP_ID).anyTimes();
+ replay(mockCoreService);
+ context.registerService(CoreService.class, mockCoreService);
+ }
+
+ /**
+ * Reads in a constraint from the given resource and decodes it.
+ *
+ * @param resourceName resource to use to read the JSON for the constraint
+ * @return decoded constraint
+ */
+ private Constraint getConstraint(String resourceName) {
+ InputStream jsonStream = ConstraintCodecTest.class
+ .getResourceAsStream(resourceName);
+ try {
+ JsonNode json = context.mapper().readTree(jsonStream);
+ assertThat(json, notNullValue());
+ Constraint constraint = constraintCodec.decode((ObjectNode) json, context);
+ assertThat(constraint, notNullValue());
+ return checkNotNull(constraint);
+ } catch (IOException ioe) {
+ Assert.fail(ioe.getMessage());
+ throw new IllegalStateException("cannot happen");
+ }
+ }
+
+
+ /**
+ * Tests link type constraint.
+ */
+ @Test
+ public void linkTypeConstraint() {
+ Constraint constraint = getConstraint("LinkTypeConstraint.json");
+ assertThat(constraint, instanceOf(LinkTypeConstraint.class));
+
+ LinkTypeConstraint linkTypeConstraint = (LinkTypeConstraint) constraint;
+ assertThat(linkTypeConstraint.isInclusive(), is(false));
+ assertThat(linkTypeConstraint.types(), hasSize(2));
+ assertThat(linkTypeConstraint.types(), hasItem(Link.Type.OPTICAL));
+ assertThat(linkTypeConstraint.types(), hasItem(Link.Type.DIRECT));
+ }
+
+ /**
+ * Tests annotation constraint.
+ */
+ @Test
+ public void annotationConstraint() {
+ Constraint constraint = getConstraint("AnnotationConstraint.json");
+ assertThat(constraint, instanceOf(AnnotationConstraint.class));
+
+ AnnotationConstraint annotationConstraint = (AnnotationConstraint) constraint;
+ assertThat(annotationConstraint.key(), is("key"));
+ assertThat(annotationConstraint.threshold(), is(123.0D));
+ }
+
+ /**
+ * Tests bandwidth constraint.
+ */
+ @Test
+ public void bandwidthConstraint() {
+ Constraint constraint = getConstraint("BandwidthConstraint.json");
+ assertThat(constraint, instanceOf(BandwidthConstraint.class));
+
+ BandwidthConstraint bandwidthConstraint = (BandwidthConstraint) constraint;
+ assertThat(bandwidthConstraint.bandwidth().toDouble(), is(345.678D));
+ }
+
+ /**
+ * Tests lambda constraint.
+ */
+ @Test
+ public void lambdaConstraint() {
+ Constraint constraint = getConstraint("LambdaConstraint.json");
+ assertThat(constraint, instanceOf(LambdaConstraint.class));
+
+ LambdaConstraint lambdaConstraint = (LambdaConstraint) constraint;
+ assertThat(lambdaConstraint.lambda().toInt(), is(444));
+ }
+
+ /**
+ * Tests latency constraint.
+ */
+ @Test
+ public void latencyConstraint() {
+ Constraint constraint = getConstraint("LatencyConstraint.json");
+ assertThat(constraint, instanceOf(LatencyConstraint.class));
+
+ LatencyConstraint latencyConstraint = (LatencyConstraint) constraint;
+ assertThat(latencyConstraint.latency().toMillis(), is(111L));
+ }
+
+ /**
+ * Tests obstacle constraint.
+ */
+ @Test
+ public void obstacleConstraint() {
+ Constraint constraint = getConstraint("ObstacleConstraint.json");
+ assertThat(constraint, instanceOf(ObstacleConstraint.class));
+
+ ObstacleConstraint obstacleConstraint = (ObstacleConstraint) constraint;
+
+ assertThat(obstacleConstraint.obstacles(), hasItem(did("dev1")));
+ assertThat(obstacleConstraint.obstacles(), hasItem(did("dev2")));
+ assertThat(obstacleConstraint.obstacles(), hasItem(did("dev3")));
+ }
+
+ /**
+ * Tests waypoint constaint.
+ */
+ @Test
+ public void waypointConstraint() {
+ Constraint constraint = getConstraint("WaypointConstraint.json");
+ assertThat(constraint, instanceOf(WaypointConstraint.class));
+
+ WaypointConstraint waypointConstraint = (WaypointConstraint) constraint;
+
+ assertThat(waypointConstraint.waypoints(), hasItem(did("devA")));
+ assertThat(waypointConstraint.waypoints(), hasItem(did("devB")));
+ assertThat(waypointConstraint.waypoints(), hasItem(did("devC")));
+ }
+
+ /**
+ * Tests asymmetric path constraint.
+ */
+ @Test
+ public void asymmetricPathConstraint() {
+ Constraint constraint = getConstraint("AsymmetricPathConstraint.json");
+ assertThat(constraint, instanceOf(AsymmetricPathConstraint.class));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
new file mode 100644
index 00000000..6bf46803
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
@@ -0,0 +1,445 @@
+/*
+ * 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.codec.impl;
+
+import java.util.EnumMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.TestUtils.getField;
+import static org.onlab.junit.TestUtils.setField;
+import static org.onosproject.codec.impl.CriterionJsonMatcher.matchesCriterion;
+
+/**
+ * Unit tests for criterion codec.
+ */
+public class CriterionCodecTest {
+
+ CodecContext context;
+ JsonCodec<Criterion> criterionCodec;
+ final PortNumber port = PortNumber.portNumber(1);
+ final IpPrefix ipPrefix4 = IpPrefix.valueOf("10.1.1.0/24");
+ final IpPrefix ipPrefix6 = IpPrefix.valueOf("fe80::/64");
+ final MacAddress mac1 = MacAddress.valueOf("00:00:11:00:00:01");
+ final TpPort tpPort = TpPort.tpPort(40000);
+
+ /**
+ * Sets up for each test. Creates a context and fetches the criterion
+ * codec.
+ */
+ @Before
+ public void setUp() {
+ context = new MockCodecContext();
+ criterionCodec = context.codec(Criterion.class);
+ assertThat(criterionCodec, notNullValue());
+ }
+
+
+ /**
+ * Checks that all criterion types are covered by the codec.
+ */
+ @Test
+ public void checkCriterionTypes() throws Exception {
+ EncodeCriterionCodecHelper encoder = new EncodeCriterionCodecHelper(
+ Criteria.dummy(), context);
+ EnumMap<Criterion.Type, Object> formatMap =
+ getField(encoder, "formatMap");
+ assertThat(formatMap, notNullValue());
+
+ for (Criterion.Type type : Criterion.Type.values()) {
+ assertThat("Entry not found for " + type.toString(),
+ formatMap.get(type), notNullValue());
+ }
+ }
+
+ /**
+ * Tests in port criterion.
+ */
+ @Test
+ public void matchInPortTest() {
+ Criterion criterion = Criteria.matchInPort(port);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests in physical port criterion.
+ */
+ @Test
+ public void matchInPhyPortTest() {
+ Criterion criterion = Criteria.matchInPhyPort(port);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests metadata criterion.
+ */
+ @Test
+ public void matchMetadataTest() {
+ Criterion criterion = Criteria.matchMetadata(0xabcdL);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ethernet destination criterion.
+ */
+ @Test
+ public void matchEthDstTest() {
+ Criterion criterion = Criteria.matchEthDst(mac1);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ethernet source criterion.
+ */
+ @Test
+ public void matchEthSrcTest() {
+ Criterion criterion = Criteria.matchEthSrc(mac1);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ethernet type criterion.
+ */
+ @Test
+ public void matchEthTypeTest() {
+ Criterion criterion = Criteria.matchEthType((short) 0x8844);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests VLAN Id criterion.
+ */
+ @Test
+ public void matchVlanIdTest() {
+ Criterion criterion = Criteria.matchVlanId(VlanId.ANY);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests VLAN PCP criterion.
+ */
+ @Test
+ public void matchVlanPcpTest() {
+ Criterion criterion = Criteria.matchVlanPcp((byte) 7);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IP DSCP criterion.
+ */
+ @Test
+ public void matchIPDscpTest() {
+ Criterion criterion = Criteria.matchIPDscp((byte) 63);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IP ECN criterion.
+ */
+ @Test
+ public void matchIPEcnTest() {
+ Criterion criterion = Criteria.matchIPEcn((byte) 3);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IP protocol criterion.
+ */
+ @Test
+ public void matchIPProtocolTest() {
+ Criterion criterion = Criteria.matchIPProtocol((byte) 250);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IP source criterion.
+ */
+ @Test
+ public void matchIPSrcTest() {
+ Criterion criterion = Criteria.matchIPSrc(ipPrefix4);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IP destination criterion.
+ */
+ @Test
+ public void matchIPDstTest() {
+ Criterion criterion = Criteria.matchIPDst(ipPrefix4);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests source TCP port criterion.
+ */
+ @Test
+ public void matchTcpSrcTest() {
+ Criterion criterion = Criteria.matchTcpSrc(tpPort);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests destination TCP port criterion.
+ */
+ @Test
+ public void matchTcpDstTest() {
+ Criterion criterion = Criteria.matchTcpDst(tpPort);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests source UDP port criterion.
+ */
+ @Test
+ public void matchUdpSrcTest() {
+ Criterion criterion = Criteria.matchUdpSrc(tpPort);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests destination UDP criterion.
+ */
+ @Test
+ public void matchUdpDstTest() {
+ Criterion criterion = Criteria.matchUdpDst(tpPort);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests source SCTP criterion.
+ */
+ @Test
+ public void matchSctpSrcTest() {
+ Criterion criterion = Criteria.matchSctpSrc(tpPort);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests destination SCTP criterion.
+ */
+ @Test
+ public void matchSctpDstTest() {
+ Criterion criterion = Criteria.matchSctpDst(tpPort);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ICMP type criterion.
+ */
+ @Test
+ public void matchIcmpTypeTest() {
+ Criterion criterion = Criteria.matchIcmpType((byte) 250);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ICMP code criterion.
+ */
+ @Test
+ public void matchIcmpCodeTest() {
+ Criterion criterion = Criteria.matchIcmpCode((byte) 250);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPv6 source criterion.
+ */
+ @Test
+ public void matchIPv6SrcTest() {
+ Criterion criterion = Criteria.matchIPv6Src(ipPrefix6);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPv6 destination criterion.
+ */
+ @Test
+ public void matchIPv6DstTest() {
+ Criterion criterion = Criteria.matchIPv6Dst(ipPrefix6);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPv6 flow label criterion.
+ */
+ @Test
+ public void matchIPv6FlowLabelTest() {
+ Criterion criterion = Criteria.matchIPv6FlowLabel(0xffffe);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ICMP v6 type criterion.
+ */
+ @Test
+ public void matchIcmpv6TypeTest() {
+ Criterion criterion = Criteria.matchIcmpv6Type((byte) 250);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests ICMP v6 code criterion.
+ */
+ @Test
+ public void matchIcmpv6CodeTest() {
+ Criterion criterion = Criteria.matchIcmpv6Code((byte) 250);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPV6 target address criterion.
+ */
+ @Test
+ public void matchIPv6NDTargetAddressTest() {
+ Criterion criterion =
+ Criteria.matchIPv6NDTargetAddress(
+ Ip6Address.valueOf("1111:2222::"));
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPV6 SLL criterion.
+ */
+ @Test
+ public void matchIPv6NDSourceLinkLayerAddressTest() {
+ Criterion criterion = Criteria.matchIPv6NDSourceLinkLayerAddress(mac1);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPV6 TLL criterion.
+ */
+ @Test
+ public void matchIPv6NDTargetLinkLayerAddressTest() {
+ Criterion criterion = Criteria.matchIPv6NDTargetLinkLayerAddress(mac1);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests MPLS label criterion.
+ */
+ @Test
+ public void matchMplsLabelTest() {
+ Criterion criterion = Criteria.matchMplsLabel(MplsLabel.mplsLabel(0xffffe));
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests IPv6 Extension Header pseudo-field flags criterion.
+ */
+ @Test
+ public void matchIPv6ExthdrFlagsTest() {
+ int exthdrFlags =
+ Criterion.IPv6ExthdrFlags.NONEXT.getValue() |
+ Criterion.IPv6ExthdrFlags.ESP.getValue() |
+ Criterion.IPv6ExthdrFlags.AUTH.getValue() |
+ Criterion.IPv6ExthdrFlags.DEST.getValue() |
+ Criterion.IPv6ExthdrFlags.FRAG.getValue() |
+ Criterion.IPv6ExthdrFlags.ROUTER.getValue() |
+ Criterion.IPv6ExthdrFlags.HOP.getValue() |
+ Criterion.IPv6ExthdrFlags.UNREP.getValue() |
+ Criterion.IPv6ExthdrFlags.UNSEQ.getValue();
+ Criterion criterion = Criteria.matchIPv6ExthdrFlags(exthdrFlags);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests lambda criterion.
+ */
+ @Test
+ public void matchOchSignal() {
+ Lambda ochSignal = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8);
+ Criterion criterion = Criteria.matchLambda(ochSignal);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests Och signal type criterion.
+ */
+ @Test
+ public void matchOchSignalTypeTest() {
+ Criterion criterion = Criteria.matchOchSignalType(OchSignalType.FIXED_GRID);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result, matchesCriterion(criterion));
+ }
+
+ /**
+ * Tests that an unimplemented criterion type only returns the type and
+ * no other data.
+ */
+ @Test
+ public void matchUnknownTypeTest() throws Exception {
+ Criterion criterion = Criteria.matchOpticalSignalType((byte) 250);
+ setField(criterion, "type", Criterion.Type.UNASSIGNED_40);
+ ObjectNode result = criterionCodec.encode(criterion, context);
+ assertThat(result.get("type").textValue(), is(criterion.type().toString()));
+ assertThat(result.size(), is(1));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
new file mode 100644
index 00000000..bb3acad5
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
@@ -0,0 +1,609 @@
+/*
+ * 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.codec.impl;
+
+import com.google.common.base.Joiner;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.flow.criteria.Criterion;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.IPDscpCriterion;
+import org.onosproject.net.flow.criteria.IPEcnCriterion;
+import org.onosproject.net.flow.criteria.IPProtocolCriterion;
+import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion;
+import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion;
+import org.onosproject.net.flow.criteria.IcmpCodeCriterion;
+import org.onosproject.net.flow.criteria.IcmpTypeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion;
+import org.onosproject.net.flow.criteria.MetadataCriterion;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.SctpPortCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.criteria.VlanPcpCriterion;
+
+import java.util.Objects;
+
+/**
+ * Hamcrest matcher for criterion objects.
+ */
+public final class CriterionJsonMatcher extends
+ TypeSafeDiagnosingMatcher<JsonNode> {
+
+ final Criterion criterion;
+ Description description;
+ JsonNode jsonCriterion;
+
+ /**
+ * Constructs a matcher object.
+ *
+ * @param criterionValue criterion to match
+ */
+ private CriterionJsonMatcher(Criterion criterionValue) {
+ criterion = criterionValue;
+ }
+
+ /**
+ * Factory to allocate an criterion matcher.
+ *
+ * @param criterion criterion object we are looking for
+ * @return matcher
+ */
+ public static CriterionJsonMatcher matchesCriterion(Criterion criterion) {
+ return new CriterionJsonMatcher(criterion);
+ }
+
+ /**
+ * Matches a port criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(PortCriterion criterion) {
+ final long port = criterion.port().toLong();
+ final long jsonPort = jsonCriterion.get("port").asLong();
+ if (port != jsonPort) {
+ description.appendText("port was " + Long.toString(jsonPort));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches a metadata criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(MetadataCriterion criterion) {
+ final long metadata = criterion.metadata();
+ final long jsonMetadata = jsonCriterion.get("metadata").asLong();
+ if (metadata != jsonMetadata) {
+ description.appendText("metadata was "
+ + Long.toString(jsonMetadata));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an eth criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(EthCriterion criterion) {
+ final String mac = criterion.mac().toString();
+ final String jsonMac = jsonCriterion.get("mac").textValue();
+ if (!mac.equals(jsonMac)) {
+ description.appendText("mac was " + jsonMac);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an eth type criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(EthTypeCriterion criterion) {
+ final int ethType = criterion.ethType().toShort();
+ final int jsonEthType = jsonCriterion.get("ethType").intValue();
+ if (ethType != jsonEthType) {
+ description.appendText("ethType was "
+ + Integer.toString(jsonEthType));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches a VLAN ID criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(VlanIdCriterion criterion) {
+ final short vlanId = criterion.vlanId().toShort();
+ final short jsonVlanId = jsonCriterion.get("vlanId").shortValue();
+ if (vlanId != jsonVlanId) {
+ description.appendText("vlanId was " + Short.toString(jsonVlanId));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches a VLAN PCP criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(VlanPcpCriterion criterion) {
+ final byte priority = criterion.priority();
+ final byte jsonPriority =
+ (byte) jsonCriterion.get("priority").shortValue();
+ if (priority != jsonPriority) {
+ description.appendText("priority was " + Byte.toString(jsonPriority));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IP DSCP criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPDscpCriterion criterion) {
+ final byte ipDscp = criterion.ipDscp();
+ final byte jsonIpDscp = (byte) jsonCriterion.get("ipDscp").shortValue();
+ if (ipDscp != jsonIpDscp) {
+ description.appendText("IP DSCP was " + Byte.toString(jsonIpDscp));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IP ECN criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPEcnCriterion criterion) {
+ final byte ipEcn = criterion.ipEcn();
+ final byte jsonIpEcn = (byte) jsonCriterion.get("ipEcn").shortValue();
+ if (ipEcn != jsonIpEcn) {
+ description.appendText("IP ECN was " + Byte.toString(jsonIpEcn));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IP protocol criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPProtocolCriterion criterion) {
+ final short protocol = criterion.protocol();
+ final short jsonProtocol = jsonCriterion.get("protocol").shortValue();
+ if (protocol != jsonProtocol) {
+ description.appendText("protocol was "
+ + Short.toString(jsonProtocol));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IP address criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPCriterion criterion) {
+ final String ip = criterion.ip().toString();
+ final String jsonIp = jsonCriterion.get("ip").textValue();
+ if (!ip.equals(jsonIp)) {
+ description.appendText("ip was " + jsonIp);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches a TCP port criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(TcpPortCriterion criterion) {
+ final int tcpPort = criterion.tcpPort().toInt();
+ final int jsonTcpPort = jsonCriterion.get("tcpPort").intValue();
+ if (tcpPort != jsonTcpPort) {
+ description.appendText("tcp port was "
+ + Integer.toString(jsonTcpPort));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches a UDP port criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(UdpPortCriterion criterion) {
+ final int udpPort = criterion.udpPort().toInt();
+ final int jsonUdpPort = jsonCriterion.get("udpPort").intValue();
+ if (udpPort != jsonUdpPort) {
+ description.appendText("udp port was "
+ + Integer.toString(jsonUdpPort));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an SCTP port criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(SctpPortCriterion criterion) {
+ final int sctpPort = criterion.sctpPort().toInt();
+ final int jsonSctpPort = jsonCriterion.get("sctpPort").intValue();
+ if (sctpPort != jsonSctpPort) {
+ description.appendText("sctp port was "
+ + Integer.toString(jsonSctpPort));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an ICMP type criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IcmpTypeCriterion criterion) {
+ final short icmpType = criterion.icmpType();
+ final short jsonIcmpType = jsonCriterion.get("icmpType").shortValue();
+ if (icmpType != jsonIcmpType) {
+ description.appendText("icmp type was "
+ + Short.toString(jsonIcmpType));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an ICMP code criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IcmpCodeCriterion criterion) {
+ final short icmpCode = criterion.icmpCode();
+ final short jsonIcmpCode = jsonCriterion.get("icmpCode").shortValue();
+ if (icmpCode != jsonIcmpCode) {
+ description.appendText("icmp code was "
+ + Short.toString(jsonIcmpCode));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IPV6 flow label criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPv6FlowLabelCriterion criterion) {
+ final int flowLabel = criterion.flowLabel();
+ final int jsonFlowLabel = jsonCriterion.get("flowLabel").intValue();
+ if (flowLabel != jsonFlowLabel) {
+ description.appendText("IPv6 flow label was "
+ + Integer.toString(jsonFlowLabel));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an ICMP V6 type criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(Icmpv6TypeCriterion criterion) {
+ final short icmpv6Type = criterion.icmpv6Type();
+ final short jsonIcmpv6Type =
+ jsonCriterion.get("icmpv6Type").shortValue();
+ if (icmpv6Type != jsonIcmpv6Type) {
+ description.appendText("icmpv6 type was "
+ + Short.toString(jsonIcmpv6Type));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IPV6 code criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(Icmpv6CodeCriterion criterion) {
+ final short icmpv6Code = criterion.icmpv6Code();
+ final short jsonIcmpv6Code =
+ jsonCriterion.get("icmpv6Code").shortValue();
+ if (icmpv6Code != jsonIcmpv6Code) {
+ description.appendText("icmpv6 code was "
+ + Short.toString(jsonIcmpv6Code));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IPV6 ND target criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPv6NDTargetAddressCriterion criterion) {
+ final String targetAddress =
+ criterion.targetAddress().toString();
+ final String jsonTargetAddress =
+ jsonCriterion.get("targetAddress").textValue();
+ if (!targetAddress.equals(jsonTargetAddress)) {
+ description.appendText("target address was " +
+ jsonTargetAddress);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IPV6 ND link layer criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPv6NDLinkLayerAddressCriterion criterion) {
+ final String llAddress =
+ criterion.mac().toString();
+ final String jsonLlAddress =
+ jsonCriterion.get("mac").textValue();
+ if (!llAddress.equals(jsonLlAddress)) {
+ description.appendText("mac was " + jsonLlAddress);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an MPLS label criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(MplsCriterion criterion) {
+ final int label = criterion.label().toInt();
+ final int jsonLabel = jsonCriterion.get("label").intValue();
+ if (label != jsonLabel) {
+ description.appendText("label was " + Integer.toString(jsonLabel));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an IPV6 exthdr criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(IPv6ExthdrFlagsCriterion criterion) {
+ final int exthdrFlags = criterion.exthdrFlags();
+ final int jsonExthdrFlags =
+ jsonCriterion.get("exthdrFlags").intValue();
+ if (exthdrFlags != jsonExthdrFlags) {
+ description.appendText("exthdrFlags was "
+ + Long.toHexString(jsonExthdrFlags));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an Och signal criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(OchSignalCriterion criterion) {
+ final OchSignal ochSignal = criterion.lambda();
+ final JsonNode jsonOchSignal = jsonCriterion.get("ochSignalId");
+ String jsonGridType = jsonOchSignal.get("gridType").textValue();
+ String jsonChannelSpacing = jsonOchSignal.get("channelSpacing").textValue();
+ int jsonSpacingMultiplier = jsonOchSignal.get("spacingMultiplier").intValue();
+ int jsonSlotGranularity = jsonOchSignal.get("slotGranularity").intValue();
+
+ boolean equality = Objects.equals(ochSignal.gridType().name(), jsonGridType)
+ && Objects.equals(ochSignal.channelSpacing().name(), jsonChannelSpacing)
+ && Objects.equals(ochSignal.spacingMultiplier(), jsonSpacingMultiplier)
+ && Objects.equals(ochSignal.slotGranularity(), jsonSlotGranularity);
+
+ if (!equality) {
+ String joined = Joiner.on(", ")
+ .join(jsonGridType, jsonChannelSpacing, jsonSpacingMultiplier, jsonSlotGranularity);
+
+ description.appendText("och signal id was " + joined);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches an Och signal type criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(OchSignalTypeCriterion criterion) {
+ final String signalType = criterion.signalType().name();
+ final String jsonSignalType = jsonCriterion.get("ochSignalType").textValue();
+ if (!signalType.equals(jsonSignalType)) {
+ description.appendText("signal type was " + jsonSignalType);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonCriterion,
+ Description description) {
+ this.description = description;
+ this.jsonCriterion = jsonCriterion;
+ final String type = criterion.type().name();
+ final String jsonType = jsonCriterion.get("type").asText();
+ if (!type.equals(jsonType)) {
+ description.appendText("type was " + type);
+ return false;
+ }
+
+ switch (criterion.type()) {
+
+ case IN_PORT:
+ case IN_PHY_PORT:
+ return matchCriterion((PortCriterion) criterion);
+
+ case METADATA:
+ return matchCriterion((MetadataCriterion) criterion);
+
+ case ETH_DST:
+ case ETH_SRC:
+ return matchCriterion((EthCriterion) criterion);
+
+ case ETH_TYPE:
+ return matchCriterion((EthTypeCriterion) criterion);
+
+ case VLAN_VID:
+ return matchCriterion((VlanIdCriterion) criterion);
+
+ case VLAN_PCP:
+ return matchCriterion((VlanPcpCriterion) criterion);
+
+ case IP_DSCP:
+ return matchCriterion((IPDscpCriterion) criterion);
+
+ case IP_ECN:
+ return matchCriterion((IPEcnCriterion) criterion);
+
+ case IP_PROTO:
+ return matchCriterion((IPProtocolCriterion) criterion);
+
+ case IPV4_SRC:
+ case IPV4_DST:
+ case IPV6_SRC:
+ case IPV6_DST:
+ return matchCriterion((IPCriterion) criterion);
+
+ case TCP_SRC:
+ case TCP_DST:
+ return matchCriterion((TcpPortCriterion) criterion);
+
+ case UDP_SRC:
+ case UDP_DST:
+ return matchCriterion((UdpPortCriterion) criterion);
+
+ case SCTP_SRC:
+ case SCTP_DST:
+ return matchCriterion((SctpPortCriterion) criterion);
+
+ case ICMPV4_TYPE:
+ return matchCriterion((IcmpTypeCriterion) criterion);
+
+ case ICMPV4_CODE:
+ return matchCriterion((IcmpCodeCriterion) criterion);
+
+ case IPV6_FLABEL:
+ return matchCriterion((IPv6FlowLabelCriterion) criterion);
+
+ case ICMPV6_TYPE:
+ return matchCriterion((Icmpv6TypeCriterion) criterion);
+
+ case ICMPV6_CODE:
+ return matchCriterion((Icmpv6CodeCriterion) criterion);
+
+ case IPV6_ND_TARGET:
+ return matchCriterion(
+ (IPv6NDTargetAddressCriterion) criterion);
+
+ case IPV6_ND_SLL:
+ case IPV6_ND_TLL:
+ return matchCriterion(
+ (IPv6NDLinkLayerAddressCriterion) criterion);
+
+ case MPLS_LABEL:
+ return matchCriterion((MplsCriterion) criterion);
+
+ case IPV6_EXTHDR:
+ return matchCriterion(
+ (IPv6ExthdrFlagsCriterion) criterion);
+
+ case OCH_SIGID:
+ return matchCriterion((OchSignalCriterion) criterion);
+
+ case OCH_SIGTYPE:
+ return matchCriterion((OchSignalTypeCriterion) criterion);
+
+ default:
+ // Don't know how to format this type
+ description.appendText("unknown criterion type " +
+ criterion.type());
+ return false;
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(criterion.toString());
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java
new file mode 100644
index 00000000..c7196e8b
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+
+/**
+ * Unit test for DeviceCodec.
+ */
+public class DeviceCodecTest {
+
+ private Device device = new DefaultDevice(JsonCodecUtils.PID,
+ JsonCodecUtils.DID1,
+ Device.Type.SWITCH,
+ JsonCodecUtils.MFR,
+ JsonCodecUtils.HW,
+ JsonCodecUtils.SW1,
+ JsonCodecUtils.SN,
+ JsonCodecUtils.CID,
+ JsonCodecUtils.A1);
+
+
+
+ @Test
+ public void deviceCodecTest() {
+ final MockCodecContext context = new MockCodecContext();
+ context.registerService(DeviceService.class, new DeviceServiceAdapter());
+ final JsonCodec<Device> codec = context.codec(Device.class);
+ assertThat(codec, is(notNullValue()));
+ final Device pojoIn = device;
+
+ assertJsonEncodable(context, codec, pojoIn);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java
new file mode 100644
index 00000000..a1c95176
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.codec.impl;
+
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.DefaultDriver;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.TestBehaviour;
+import org.onosproject.net.driver.TestBehaviourImpl;
+import org.onosproject.net.driver.TestBehaviourTwo;
+import org.onosproject.net.driver.TestBehaviourTwoImpl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.codec.impl.DriverJsonMatcher.matchesDriver;
+
+/**
+ * Unit tests for the driver codec.
+ */
+public class DriverCodecTest {
+
+ @Test
+ public void codecTest() {
+ Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class,
+ TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class);
+ Map<String, String> properties =
+ ImmutableMap.of("key1", "value1", "key2", "value2");
+
+ DefaultDriver parent = new DefaultDriver("parent", null, "Acme",
+ "HW1.2.3", "SW1.2.3",
+ behaviours,
+ properties);
+ DefaultDriver child = new DefaultDriver("child", parent, "Acme",
+ "HW1.2.3.1", "SW1.2.3.1",
+ behaviours,
+ properties);
+
+ MockCodecContext context = new MockCodecContext();
+ ObjectNode driverJson = context.codec(Driver.class).encode(child, context);
+
+ assertThat(driverJson, matchesDriver(child));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java
new file mode 100644
index 00000000..6f0070e5
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java
@@ -0,0 +1,118 @@
+/*
+ * 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.codec.impl;
+
+import java.util.Map;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.driver.Driver;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for drivers.
+ */
+public final class DriverJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+ private final Driver driver;
+
+ private DriverJsonMatcher(Driver driver) {
+ this.driver = driver;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonDriver, Description description) {
+ // check id
+ String jsonDriverName = jsonDriver.get("name").asText();
+ String driverName = driver.name();
+ if (!jsonDriverName.equals(driverName)) {
+ description.appendText("name was " + jsonDriverName);
+ return false;
+ }
+
+
+ // check parent
+ String jsonParent = jsonDriver.get("parent").asText();
+ String parent = driver.parent().name();
+ if (!jsonParent.equals(parent)) {
+ description.appendText("parent was " + jsonParent);
+ return false;
+ }
+
+ // check manufacturer
+ String jsonManufacturer = jsonDriver.get("manufacturer").asText();
+ String manufacturer = driver.manufacturer();
+ if (!jsonManufacturer.equals(manufacturer)) {
+ description.appendText("manufacturer was " + jsonManufacturer);
+ return false;
+ }
+
+ // check HW version
+ String jsonHWVersion = jsonDriver.get("hwVersion").asText();
+ String hwVersion = driver.hwVersion();
+ if (!jsonHWVersion.equals(hwVersion)) {
+ description.appendText("HW version was " + jsonHWVersion);
+ return false;
+ }
+
+ // check SW version
+ String jsonSWVersion = jsonDriver.get("swVersion").asText();
+ String swVersion = driver.swVersion();
+ if (!jsonSWVersion.equals(swVersion)) {
+ description.appendText("SW version was " + jsonSWVersion);
+ return false;
+ }
+
+ // Check properties
+ JsonNode jsonProperties = jsonDriver.get("properties");
+ if (driver.properties().size() != jsonProperties.size()) {
+ description.appendText("properties map size was was " + jsonProperties.size());
+ return false;
+ }
+ for (Map.Entry<String, String> entry : driver.properties().entrySet()) {
+ boolean propertyFound = false;
+ for (int propertyIndex = 0; propertyIndex < jsonProperties.size(); propertyIndex++) {
+ String jsonName = jsonProperties.get(propertyIndex).get("name").asText();
+ String jsonValue = jsonProperties.get(propertyIndex).get("value").asText();
+ if (!jsonName.equals(entry.getKey()) ||
+ !jsonValue.equals(entry.getValue())) {
+ propertyFound = true;
+ break;
+ }
+ }
+ if (!propertyFound) {
+ description.appendText("property not found " + entry.getKey());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(driver.toString());
+ }
+
+ /**
+ * Factory to allocate a driver matcher.
+ *
+ * @param driver driver object we are looking for
+ * @return matcher
+ */
+ public static DriverJsonMatcher matchesDriver(Driver driver) {
+ return new DriverJsonMatcher(driver);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java
new file mode 100644
index 00000000..847b0d09
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.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.codec.impl;
+
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.EthernetJsonMatcher.matchesEthernet;
+
+/**
+ * Unit test for Ethernet class codec.
+ */
+public class EthernetCodecTest {
+
+ /**
+ * Unit test for the ethernet object codec.
+ */
+ @Test
+ public void ethernetCodecTest() {
+ final CodecContext context = new MockCodecContext();
+ final JsonCodec<Ethernet> ethernetCodec = context.codec(Ethernet.class);
+ assertThat(ethernetCodec, notNullValue());
+
+ final Ethernet eth1 = new Ethernet();
+ eth1.setSourceMACAddress("11:22:33:44:55:01");
+ eth1.setDestinationMACAddress("11:22:33:44:55:02");
+ eth1.setPad(true);
+ eth1.setEtherType(Ethernet.TYPE_ARP);
+ eth1.setPriorityCode((byte) 7);
+ eth1.setVlanID((short) 33);
+
+ final ObjectNode eth1Json = ethernetCodec.encode(eth1, context);
+ assertThat(eth1Json, notNullValue());
+ assertThat(eth1Json, matchesEthernet(eth1));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java
new file mode 100644
index 00000000..c5827b91
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java
@@ -0,0 +1,122 @@
+/*
+ * 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.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.onlab.packet.Ethernet;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for ethernet objects.
+ */
+public final class EthernetJsonMatcher extends TypeSafeMatcher<JsonNode> {
+
+ private final Ethernet ethernet;
+ private String reason = "";
+
+ private EthernetJsonMatcher(Ethernet ethernetValue) {
+ ethernet = ethernetValue;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonEthernet) {
+
+ // check source MAC
+ final JsonNode jsonSourceMacNode = jsonEthernet.get("srcMac");
+ if (ethernet.getSourceMAC() != null) {
+ final String jsonSourceMac = jsonSourceMacNode.textValue();
+ final String sourceMac = ethernet.getSourceMAC().toString();
+ if (!jsonSourceMac.equals(sourceMac)) {
+ reason = "source MAC " + ethernet.getSourceMAC().toString();
+ return false;
+ }
+ } else {
+ // source MAC not specified, JSON representation must be empty
+ if (jsonSourceMacNode != null) {
+ reason = "source mac should be null ";
+ return false;
+ }
+ }
+
+ // check destination MAC
+ final JsonNode jsonDestinationMacNode = jsonEthernet.get("destMac");
+ if (ethernet.getDestinationMAC() != null) {
+ final String jsonDestinationMac = jsonDestinationMacNode.textValue();
+ final String destinationMac = ethernet.getDestinationMAC().toString();
+ if (!jsonDestinationMac.equals(destinationMac)) {
+ reason = "destination MAC " + ethernet.getDestinationMAC().toString();
+ return false;
+ }
+ } else {
+ // destination MAC not specified, JSON representation must be empty
+ if (jsonDestinationMacNode != null) {
+ reason = "destination mac should be null ";
+ return false;
+ }
+ }
+
+ // check priority code
+ final short jsonPriorityCode = jsonEthernet.get("priorityCode").shortValue();
+ final short priorityCode = ethernet.getPriorityCode();
+ if (jsonPriorityCode != priorityCode) {
+ reason = "priority code " + Short.toString(ethernet.getPriorityCode());
+ return false;
+ }
+
+ // check vlanId
+ final short jsonVlanId = jsonEthernet.get("vlanId").shortValue();
+ final short vlanId = ethernet.getVlanID();
+ if (jsonVlanId != vlanId) {
+ reason = "vlan id " + Short.toString(ethernet.getVlanID());
+ return false;
+ }
+
+ // check etherType
+ final short jsonEtherType = jsonEthernet.get("etherType").shortValue();
+ final short etherType = ethernet.getEtherType();
+ if (jsonEtherType != etherType) {
+ reason = "etherType " + Short.toString(ethernet.getEtherType());
+ return false;
+ }
+
+ // check pad
+ final boolean jsonPad = jsonEthernet.get("pad").asBoolean();
+ final boolean pad = ethernet.isPad();
+ if (jsonPad != pad) {
+ reason = "pad " + Boolean.toString(ethernet.isPad());
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(reason);
+ }
+
+ /**
+ * Factory to allocate a ethernet matcher.
+ *
+ * @param ethernet ethernet object we are looking for
+ * @return matcher
+ */
+ public static EthernetJsonMatcher matchesEthernet(Ethernet ethernet) {
+ return new EthernetJsonMatcher(ethernet);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
new file mode 100644
index 00000000..6c88ac1e
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
@@ -0,0 +1,546 @@
+/*
+ * 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.codec.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.IPDscpCriterion;
+import org.onosproject.net.flow.criteria.IPEcnCriterion;
+import org.onosproject.net.flow.criteria.IPProtocolCriterion;
+import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion;
+import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion;
+import org.onosproject.net.flow.criteria.IcmpCodeCriterion;
+import org.onosproject.net.flow.criteria.IcmpTypeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion;
+import org.onosproject.net.flow.criteria.IndexedLambdaCriterion;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.SctpPortCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.TunnelIdCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.criteria.VlanPcpCriterion;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+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.flow.instructions.L4ModificationInstruction;
+
+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.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.net.NetTestTools.APP_ID;
+
+/**
+ * Flow rule codec unit tests.
+ */
+public class FlowRuleCodecTest {
+
+ MockCodecContext context;
+ JsonCodec<FlowRule> flowRuleCodec;
+ final CoreService mockCoreService = createMock(CoreService.class);
+
+ /**
+ * Sets up for each test. Creates a context and fetches the flow rule
+ * codec.
+ */
+ @Before
+ public void setUp() {
+ context = new MockCodecContext();
+ flowRuleCodec = context.codec(FlowRule.class);
+ assertThat(flowRuleCodec, notNullValue());
+
+ expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
+ .andReturn(APP_ID).anyTimes();
+ replay(mockCoreService);
+ context.registerService(CoreService.class, mockCoreService);
+ }
+
+ /**
+ * Reads in a rule from the given resource and decodes it.
+ *
+ * @param resourceName resource to use to read the JSON for the rule
+ * @return decoded flow rule
+ * @throws IOException if processing the resource fails
+ */
+ private FlowRule getRule(String resourceName) throws IOException {
+ InputStream jsonStream = FlowRuleCodecTest.class
+ .getResourceAsStream(resourceName);
+ JsonNode json = context.mapper().readTree(jsonStream);
+ assertThat(json, notNullValue());
+ FlowRule rule = flowRuleCodec.decode((ObjectNode) json, context);
+ assertThat(rule, notNullValue());
+ return rule;
+ }
+
+ /**
+ * Checks that the data shared by all the resources is correct for a
+ * given rule.
+ *
+ * @param rule rule to check
+ */
+ private void checkCommonData(FlowRule rule) {
+ assertThat(rule.appId(), is(APP_ID.id()));
+ assertThat(rule.isPermanent(), is(false));
+ assertThat(rule.timeout(), is(1));
+ assertThat(rule.priority(), is(1));
+ assertThat(rule.deviceId().toString(), is("of:0000000000000001"));
+ }
+
+ /**
+ * Checks that a simple rule decodes properly.
+ *
+ * @throws IOException if the resource cannot be processed
+ */
+ @Test
+ public void codecSimpleFlowTest() throws IOException {
+ FlowRule rule = getRule("simple-flow.json");
+
+ checkCommonData(rule);
+
+ assertThat(rule.selector().criteria().size(), is(1));
+ Criterion criterion1 = rule.selector().criteria().iterator().next();
+ assertThat(criterion1.type(), is(Criterion.Type.ETH_TYPE));
+ assertThat(((EthTypeCriterion) criterion1).ethType(), is(new EthType(2054)));
+
+ assertThat(rule.treatment().allInstructions().size(), is(1));
+ Instruction instruction1 = rule.treatment().allInstructions().get(0);
+ assertThat(instruction1.type(), is(Instruction.Type.OUTPUT));
+ assertThat(((Instructions.OutputInstruction) instruction1).port(), is(PortNumber.CONTROLLER));
+ }
+
+ SortedMap<String, Instruction> instructions = new TreeMap<>();
+
+ /**
+ * Looks up an instruction in the instruction map based on type and subtype.
+ *
+ * @param type type string
+ * @param subType subtype string
+ * @return instruction that matches
+ */
+ private Instruction getInstruction(Instruction.Type type, String subType) {
+ Instruction instruction = instructions.get(type.name() + "/" + subType);
+ assertThat(instruction, notNullValue());
+ assertThat(instruction.type(), is(type));
+ return instruction;
+ }
+
+ /**
+ * Checks that a rule with one of each instruction type decodes properly.
+ *
+ * @throws IOException if the resource cannot be processed
+ */
+ @Test
+ public void decodeInstructionsFlowTest() throws Exception {
+ FlowRule rule = getRule("instructions-flow.json");
+
+ checkCommonData(rule);
+
+ rule.treatment().allInstructions()
+ .stream()
+ .forEach(instruction ->
+ {
+ String subType;
+ if (instruction.type() == Instruction.Type.L0MODIFICATION) {
+ subType = ((L0ModificationInstruction) instruction)
+ .subtype().name();
+ } else if (instruction.type() == Instruction.Type.L2MODIFICATION) {
+ subType = ((L2ModificationInstruction) instruction)
+ .subtype().name();
+ } else if (instruction.type() == Instruction.Type.L3MODIFICATION) {
+ subType = ((L3ModificationInstruction) instruction)
+ .subtype().name();
+ } else if (instruction.type() == Instruction.Type.L4MODIFICATION) {
+ subType = ((L4ModificationInstruction) instruction)
+ .subtype().name();
+ } else {
+ subType = "";
+ }
+ instructions.put(
+ instruction.type().name() + "/" + subType, instruction);
+ });
+
+ assertThat(rule.treatment().allInstructions().size(), is(24));
+
+ Instruction instruction;
+
+ instruction = getInstruction(Instruction.Type.OUTPUT, "");
+ assertThat(instruction.type(), is(Instruction.Type.OUTPUT));
+ assertThat(((Instructions.OutputInstruction) instruction).port(), is(PortNumber.CONTROLLER));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.ETH_SRC.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.ModEtherInstruction) instruction).mac(),
+ is(MacAddress.valueOf("12:34:56:78:90:12")));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.ETH_DST.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.ModEtherInstruction) instruction).mac(),
+ is(MacAddress.valueOf("98:76:54:32:01:00")));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.VLAN_ID.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.ModVlanIdInstruction) instruction).vlanId().toShort(),
+ is((short) 22));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.VLAN_PCP.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.ModVlanPcpInstruction) instruction).vlanPcp(),
+ is((byte) 1));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.MPLS_LABEL.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.ModMplsLabelInstruction) instruction)
+ .mplsLabel().toInt(),
+ is(MplsLabel.MAX_MPLS));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.MPLS_PUSH.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.PushHeaderInstructions) instruction)
+ .ethernetType().toShort(),
+ is(Ethernet.MPLS_UNICAST));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.MPLS_POP.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.PushHeaderInstructions) instruction)
+ .ethernetType().toShort(),
+ is(Ethernet.MPLS_UNICAST));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.DEC_MPLS_TTL.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(instruction, instanceOf(L2ModificationInstruction.ModMplsTtlInstruction.class));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.VLAN_POP.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(instruction, instanceOf(L2ModificationInstruction.PopVlanInstruction.class));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.VLAN_PUSH.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(instruction, instanceOf(L2ModificationInstruction.PushHeaderInstructions.class));
+
+ instruction = getInstruction(Instruction.Type.L2MODIFICATION,
+ L2ModificationInstruction.L2SubType.TUNNEL_ID.name());
+ assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION));
+ assertThat(((L2ModificationInstruction.ModTunnelIdInstruction) instruction)
+ .tunnelId(), is(100L));
+
+ instruction = getInstruction(Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.L3SubType.IPV4_SRC.name());
+ assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION));
+ assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(),
+ is(IpAddress.valueOf("1.2.3.4")));
+
+ instruction = getInstruction(Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.L3SubType.IPV4_DST.name());
+ assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION));
+ assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(),
+ is(IpAddress.valueOf("1.2.3.3")));
+
+ instruction = getInstruction(Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.L3SubType.IPV6_SRC.name());
+ assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION));
+ assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(),
+ is(IpAddress.valueOf("1.2.3.2")));
+
+ instruction = getInstruction(Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.L3SubType.IPV6_DST.name());
+ assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION));
+ assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(),
+ is(IpAddress.valueOf("1.2.3.1")));
+
+ instruction = getInstruction(Instruction.Type.L3MODIFICATION,
+ L3ModificationInstruction.L3SubType.IPV6_FLABEL.name());
+ assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION));
+ assertThat(((L3ModificationInstruction.ModIPv6FlowLabelInstruction) instruction)
+ .flowLabel(),
+ is(8));
+
+ instruction = getInstruction(Instruction.Type.L0MODIFICATION,
+ L0ModificationInstruction.L0SubType.LAMBDA.name());
+ assertThat(instruction.type(), is(Instruction.Type.L0MODIFICATION));
+ assertThat(((L0ModificationInstruction.ModLambdaInstruction) instruction)
+ .lambda(),
+ is((short) 7));
+
+ instruction = getInstruction(Instruction.Type.L0MODIFICATION,
+ L0ModificationInstruction.L0SubType.OCH.name());
+ assertThat(instruction.type(), is(Instruction.Type.L0MODIFICATION));
+ L0ModificationInstruction.ModOchSignalInstruction och =
+ (L0ModificationInstruction.ModOchSignalInstruction) instruction;
+ assertThat(och.lambda().spacingMultiplier(), is(4));
+ assertThat(och.lambda().slotGranularity(), is(8));
+ assertThat(och.lambda().gridType(), is(GridType.DWDM));
+ assertThat(och.lambda().channelSpacing(), is(ChannelSpacing.CHL_100GHZ));
+
+ instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.L4SubType.TCP_DST.name());
+ assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+ assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+ .port().toInt(), is(40001));
+
+ instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.L4SubType.TCP_SRC.name());
+ assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+ assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+ .port().toInt(), is(40002));
+
+ instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.L4SubType.UDP_DST.name());
+ assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+ assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+ .port().toInt(), is(40003));
+
+ instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+ L4ModificationInstruction.L4SubType.UDP_SRC.name());
+ assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+ assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+ .port().toInt(), is(40004));
+ }
+
+ SortedMap<String, Criterion> criteria = new TreeMap<>();
+
+ /**
+ * Looks up a criterion in the instruction map based on type and subtype.
+ *
+ * @param type type string
+ * @return criterion that matches
+ */
+ private Criterion getCriterion(Criterion.Type type) {
+ Criterion criterion = criteria.get(type.name());
+ assertThat(criterion.type(), is(type));
+ return criterion;
+ }
+
+ /**
+ * Checks that a rule with one of each kind of criterion decodes properly.
+ *
+ * @throws IOException if the resource cannot be processed
+ */
+ @Test
+ public void codecCriteriaFlowTest() throws Exception {
+ FlowRule rule = getRule("criteria-flow.json");
+
+ checkCommonData(rule);
+
+ assertThat(rule.selector().criteria().size(), is(33));
+
+ rule.selector().criteria()
+ .stream()
+ .forEach(criterion ->
+ criteria.put(criterion.type().name(), criterion));
+
+ Criterion criterion;
+
+ criterion = getCriterion(Criterion.Type.ETH_TYPE);
+ assertThat(((EthTypeCriterion) criterion).ethType(), is(new EthType(2054)));
+
+ criterion = getCriterion(Criterion.Type.ETH_DST);
+ assertThat(((EthCriterion) criterion).mac(),
+ is(MacAddress.valueOf("00:11:22:33:44:55")));
+
+ criterion = getCriterion(Criterion.Type.ETH_SRC);
+ assertThat(((EthCriterion) criterion).mac(),
+ is(MacAddress.valueOf("00:11:22:33:44:55")));
+
+ criterion = getCriterion(Criterion.Type.IN_PORT);
+ assertThat(((PortCriterion) criterion).port(),
+ is(PortNumber.portNumber(23)));
+
+ criterion = getCriterion(Criterion.Type.IN_PHY_PORT);
+ assertThat(((PortCriterion) criterion).port(),
+ is(PortNumber.portNumber(44)));
+
+ criterion = getCriterion(Criterion.Type.VLAN_VID);
+ assertThat(((VlanIdCriterion) criterion).vlanId(),
+ is(VlanId.vlanId((short) 777)));
+
+ criterion = getCriterion(Criterion.Type.VLAN_PCP);
+ assertThat(((VlanPcpCriterion) criterion).priority(),
+ is(((byte) 3)));
+
+ criterion = getCriterion(Criterion.Type.IP_DSCP);
+ assertThat(((IPDscpCriterion) criterion).ipDscp(),
+ is(((byte) 2)));
+
+ criterion = getCriterion(Criterion.Type.IP_ECN);
+ assertThat(((IPEcnCriterion) criterion).ipEcn(),
+ is(((byte) 1)));
+
+ criterion = getCriterion(Criterion.Type.IP_PROTO);
+ assertThat(((IPProtocolCriterion) criterion).protocol(),
+ is(((short) 4)));
+
+ criterion = getCriterion(Criterion.Type.IPV4_SRC);
+ assertThat(((IPCriterion) criterion).ip(),
+ is((IpPrefix.valueOf("1.2.0.0/32"))));
+
+ criterion = getCriterion(Criterion.Type.IPV4_DST);
+ assertThat(((IPCriterion) criterion).ip(),
+ is((IpPrefix.valueOf("2.2.0.0/32"))));
+
+ criterion = getCriterion(Criterion.Type.IPV6_SRC);
+ assertThat(((IPCriterion) criterion).ip(),
+ is((IpPrefix.valueOf("3.2.0.0/32"))));
+
+ criterion = getCriterion(Criterion.Type.IPV6_DST);
+ assertThat(((IPCriterion) criterion).ip(),
+ is((IpPrefix.valueOf("4.2.0.0/32"))));
+
+ criterion = getCriterion(Criterion.Type.TCP_SRC);
+ assertThat(((TcpPortCriterion) criterion).tcpPort().toInt(),
+ is(80));
+
+ criterion = getCriterion(Criterion.Type.TCP_DST);
+ assertThat(((TcpPortCriterion) criterion).tcpPort().toInt(),
+ is(443));
+
+ criterion = getCriterion(Criterion.Type.UDP_SRC);
+ assertThat(((UdpPortCriterion) criterion).udpPort().toInt(),
+ is(180));
+
+ criterion = getCriterion(Criterion.Type.UDP_DST);
+ assertThat(((UdpPortCriterion) criterion).udpPort().toInt(),
+ is(1443));
+
+ criterion = getCriterion(Criterion.Type.SCTP_SRC);
+ assertThat(((SctpPortCriterion) criterion).sctpPort().toInt(),
+ is(280));
+
+ criterion = getCriterion(Criterion.Type.SCTP_DST);
+ assertThat(((SctpPortCriterion) criterion).sctpPort().toInt(),
+ is(2443));
+
+ criterion = getCriterion(Criterion.Type.ICMPV4_TYPE);
+ assertThat(((IcmpTypeCriterion) criterion).icmpType(),
+ is((short) 24));
+
+ criterion = getCriterion(Criterion.Type.ICMPV4_CODE);
+ assertThat(((IcmpCodeCriterion) criterion).icmpCode(),
+ is((short) 16));
+
+ criterion = getCriterion(Criterion.Type.ICMPV6_TYPE);
+ assertThat(((Icmpv6TypeCriterion) criterion).icmpv6Type(),
+ is((short) 14));
+
+ criterion = getCriterion(Criterion.Type.ICMPV6_CODE);
+ assertThat(((Icmpv6CodeCriterion) criterion).icmpv6Code(),
+ is((short) 6));
+
+ criterion = getCriterion(Criterion.Type.IPV6_FLABEL);
+ assertThat(((IPv6FlowLabelCriterion) criterion).flowLabel(),
+ is(8));
+
+ criterion = getCriterion(Criterion.Type.IPV6_ND_TARGET);
+ assertThat(((IPv6NDTargetAddressCriterion) criterion)
+ .targetAddress().toString(),
+ is("1111:2222:3333:4444:5555:6666:7777:8888"));
+
+ criterion = getCriterion(Criterion.Type.IPV6_ND_SLL);
+ assertThat(((IPv6NDLinkLayerAddressCriterion) criterion).mac(),
+ is(MacAddress.valueOf("00:11:22:33:44:56")));
+
+ criterion = getCriterion(Criterion.Type.IPV6_ND_TLL);
+ assertThat(((IPv6NDLinkLayerAddressCriterion) criterion).mac(),
+ is(MacAddress.valueOf("00:11:22:33:44:57")));
+
+ criterion = getCriterion(Criterion.Type.MPLS_LABEL);
+ assertThat(((MplsCriterion) criterion).label(),
+ is(MplsLabel.mplsLabel(123)));
+
+ criterion = getCriterion(Criterion.Type.IPV6_EXTHDR);
+ assertThat(((IPv6ExthdrFlagsCriterion) criterion).exthdrFlags(),
+ is(99));
+
+ criterion = getCriterion(Criterion.Type.OCH_SIGID);
+ assertThat(((IndexedLambdaCriterion) criterion).lambda(),
+ is(Lambda.indexedLambda(122)));
+
+ criterion = getCriterion(Criterion.Type.TUNNEL_ID);
+ assertThat(((TunnelIdCriterion) criterion).tunnelId(),
+ is(100L));
+ }
+
+ /**
+ * Checks that a rule with a SigId criterion decodes properly.
+ *
+ * @throws IOException if the resource cannot be processed
+ */
+ @Test
+ public void codecSigIdCriteriaFlowTest() throws Exception {
+ FlowRule rule = getRule("sigid-flow.json");
+
+ checkCommonData(rule);
+
+ assertThat(rule.selector().criteria().size(), is(1));
+ Criterion criterion = rule.selector().criteria().iterator().next();
+ assertThat(criterion.type(), is(Criterion.Type.OCH_SIGID));
+ Lambda lambda = ((OchSignalCriterion) criterion).lambda();
+ assertThat(lambda, instanceOf(OchSignal.class));
+ OchSignal ochSignal = (OchSignal) lambda;
+ assertThat(ochSignal.spacingMultiplier(), is(3));
+ assertThat(ochSignal.slotGranularity(), is(4));
+ assertThat(ochSignal.gridType(), is(GridType.CWDM));
+ assertThat(ochSignal.channelSpacing(), is(ChannelSpacing.CHL_25GHZ));
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java
new file mode 100644
index 00000000..b3056216
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java
@@ -0,0 +1,87 @@
+/*
+ * 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.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for instructions.
+ */
+public final class GroupBucketJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final GroupBucket bucket;
+
+ private GroupBucketJsonMatcher(GroupBucket bucket) {
+ this.bucket = bucket;
+ }
+
+ /**
+ * Matches the contents of a group bucket.
+ *
+ * @param bucketJson JSON representation of bucket to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ @Override
+ public boolean matchesSafely(JsonNode bucketJson, Description description) {
+
+ // check type
+ final String jsonType = bucketJson.get("type").textValue();
+ if (!bucket.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final long jsonWeight = bucketJson.get("weight").longValue();
+ if (bucket.weight() != jsonWeight) {
+ description.appendText("weight was " + jsonWeight);
+ return false;
+ }
+
+ final long packetsJson = bucketJson.get("packets").asLong();
+ if (bucket.packets() != packetsJson) {
+ description.appendText("packets was " + packetsJson);
+ return false;
+ }
+
+ final long bytesJson = bucketJson.get("bytes").asLong();
+ if (bucket.bytes() != bytesJson) {
+ description.appendText("bytes was " + packetsJson);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(bucket.toString());
+ }
+
+ /**
+ * Factory to allocate an bucket matcher.
+ *
+ * @param bucket bucket object we are looking for
+ * @return matcher
+ */
+ public static GroupBucketJsonMatcher matchesGroupBucket(GroupBucket bucket) {
+ return new GroupBucketJsonMatcher(bucket);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java
new file mode 100644
index 00000000..409f8eb3
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.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.codec.impl;
+
+import org.junit.Test;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup;
+
+/**
+ * Group codec unit tests.
+ */
+
+public class GroupCodecTest {
+
+ @Test
+ public void codecTest() {
+ GroupBucket bucket1 = DefaultGroupBucket
+ .createSelectGroupBucket(DefaultTrafficTreatment.emptyTreatment());
+ GroupBucket bucket2 = DefaultGroupBucket
+ .createIndirectGroupBucket(DefaultTrafficTreatment.emptyTreatment());
+ GroupBuckets buckets = new GroupBuckets(ImmutableList.of(bucket1, bucket2));
+
+
+ DefaultGroup group = new DefaultGroup(
+ new DefaultGroupId(1),
+ NetTestTools.did("d1"),
+ GroupDescription.Type.INDIRECT,
+ buckets);
+
+ MockCodecContext context = new MockCodecContext();
+ GroupCodec codec = new GroupCodec();
+ ObjectNode groupJson = codec.encode(group, context);
+
+ assertThat(groupJson, matchesGroup(group));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java
new file mode 100644
index 00000000..0e62c305
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java
@@ -0,0 +1,120 @@
+/*
+ * 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.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for groups.
+ */
+
+public final class GroupJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final Group group;
+
+ private GroupJsonMatcher(Group group) {
+ this.group = group;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonGroup, Description description) {
+ // check id
+ String jsonGroupId = jsonGroup.get("id").asText();
+ String groupId = group.id().toString();
+ if (!jsonGroupId.equals(groupId)) {
+ description.appendText("group id was " + jsonGroupId);
+ return false;
+ }
+
+ // check state
+ String jsonState = jsonGroup.get("state").asText();
+ String state = group.state().toString();
+ if (!jsonState.equals(state)) {
+ description.appendText("state was " + jsonState);
+ return false;
+ }
+
+ // check life
+ long jsonLife = jsonGroup.get("life").asLong();
+ long life = group.life();
+ if (life != jsonLife) {
+ description.appendText("life was " + jsonLife);
+ return false;
+ }
+
+ // check bytes
+ long jsonBytes = jsonGroup.get("bytes").asLong();
+ long bytes = group.bytes();
+ if (bytes != jsonBytes) {
+ description.appendText("bytes was " + jsonBytes);
+ return false;
+ }
+
+ // check packets
+ long jsonPackets = jsonGroup.get("packets").asLong();
+ long packets = group.packets();
+ if (packets != jsonPackets) {
+ description.appendText("packets was " + jsonPackets);
+ return false;
+ }
+
+ // check size of bucket array
+ JsonNode jsonBuckets = jsonGroup.get("buckets");
+ if (jsonBuckets.size() != group.buckets().buckets().size()) {
+ description.appendText("buckets size was " + jsonBuckets.size());
+ return false;
+ }
+
+ // Check buckets
+ for (GroupBucket bucket : group.buckets().buckets()) {
+ boolean bucketFound = false;
+ for (int bucketIndex = 0; bucketIndex < jsonBuckets.size(); bucketIndex++) {
+ GroupBucketJsonMatcher bucketMatcher =
+ GroupBucketJsonMatcher.matchesGroupBucket(bucket);
+ if (bucketMatcher.matches(jsonBuckets.get(bucketIndex))) {
+ bucketFound = true;
+ break;
+ }
+ }
+ if (!bucketFound) {
+ description.appendText("bucket not found " + bucket.toString());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(group.toString());
+ }
+
+ /**
+ * Factory to allocate a group matcher.
+ *
+ * @param group group object we are looking for
+ * @return matcher
+ */
+ public static GroupJsonMatcher matchesGroup(Group group) {
+ return new GroupJsonMatcher(group);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java
new file mode 100644
index 00000000..2081fa58
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.codec.impl;
+
+import org.junit.Test;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
+
+/**
+ * Tests to assure that the codec classes follow the contract of having
+ * no local context.
+ */
+public class ImmutableCodecsTest {
+
+ /**
+ * Checks that the codec classes adhere to the contract that there cannot
+ * be any local context in a codec.
+ */
+ @Test
+ public void checkImmutability() {
+ assertThatClassIsImmutableBaseClass(AnnotatedCodec.class);
+ assertThatClassIsImmutable(AnnotationsCodec.class);
+ assertThatClassIsImmutable(ApplicationCodec.class);
+ assertThatClassIsImmutable(ConnectivityIntentCodec.class);
+ assertThatClassIsImmutable(ConnectPointCodec.class);
+ assertThatClassIsImmutable(ConstraintCodec.class);
+ assertThatClassIsImmutable(EncodeConstraintCodecHelper.class);
+ assertThatClassIsImmutable(DecodeConstraintCodecHelper.class);
+ assertThatClassIsImmutable(CriterionCodec.class);
+ assertThatClassIsImmutable(EncodeCriterionCodecHelper.class);
+ assertThatClassIsImmutable(DecodeCriterionCodecHelper.class);
+ assertThatClassIsImmutable(DeviceCodec.class);
+ assertThatClassIsImmutable(EthernetCodec.class);
+ assertThatClassIsImmutable(FlowEntryCodec.class);
+ assertThatClassIsImmutable(HostCodec.class);
+ assertThatClassIsImmutable(HostLocationCodec.class);
+ assertThatClassIsImmutable(HostToHostIntentCodec.class);
+ assertThatClassIsImmutable(InstructionCodec.class);
+ assertThatClassIsImmutable(EncodeInstructionCodecHelper.class);
+ assertThatClassIsImmutable(DecodeInstructionCodecHelper.class);
+ assertThatClassIsImmutable(IntentCodec.class);
+ assertThatClassIsImmutable(LinkCodec.class);
+ assertThatClassIsImmutable(PathCodec.class);
+ assertThatClassIsImmutable(PointToPointIntentCodec.class);
+ assertThatClassIsImmutable(PortCodec.class);
+ assertThatClassIsImmutable(TopologyClusterCodec.class);
+ assertThatClassIsImmutable(TopologyCodec.class);
+ assertThatClassIsImmutable(TrafficSelectorCodec.class);
+ assertThatClassIsImmutable(TrafficTreatmentCodec.class);
+ assertThatClassIsImmutable(FlowRuleCodec.class);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
new file mode 100644
index 00000000..bafbc0f1
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.codec.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L0ModificationInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.InstructionJsonMatcher.matchesInstruction;
+
+/**
+ * Unit tests for Instruction codec.
+ */
+public class InstructionCodecTest {
+ CodecContext context;
+ JsonCodec<Instruction> instructionCodec;
+
+ /**
+ * Sets up for each test. Creates a context and fetches the instruction
+ * codec.
+ */
+ @Before
+ public void setUp() {
+ context = new MockCodecContext();
+ instructionCodec = context.codec(Instruction.class);
+ assertThat(instructionCodec, notNullValue());
+ }
+
+ /**
+ * Tests the encoding of push header instructions.
+ */
+ @Test
+ public void pushHeaderInstructionsTest() {
+ final L2ModificationInstruction.PushHeaderInstructions instruction =
+ (L2ModificationInstruction.PushHeaderInstructions) Instructions.pushMpls();
+ final ObjectNode instructionJson = instructionCodec.encode(instruction, context);
+
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of drop instructions.
+ */
+ @Test
+ public void dropInstructionTest() {
+ final Instructions.DropInstruction instruction =
+ Instructions.createDrop();
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of output instructions.
+ */
+ @Test
+ public void outputInstructionTest() {
+ final Instructions.OutputInstruction instruction =
+ Instructions.createOutput(PortNumber.portNumber(22));
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod lambda instructions.
+ */
+ @Test
+ public void modLambdaInstructionTest() {
+ final L0ModificationInstruction.ModLambdaInstruction instruction =
+ (L0ModificationInstruction.ModLambdaInstruction)
+ Instructions.modL0Lambda(new IndexedLambda(55));
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod OCh signal instructions.
+ */
+ @Test
+ public void modOchSignalInstructionTest() {
+ L0ModificationInstruction.ModOchSignalInstruction instruction =
+ (L0ModificationInstruction.ModOchSignalInstruction)
+ Instructions.modL0Lambda(Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8));
+ ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod ether instructions.
+ */
+ @Test
+ public void modEtherInstructionTest() {
+ final L2ModificationInstruction.ModEtherInstruction instruction =
+ (L2ModificationInstruction.ModEtherInstruction)
+ Instructions.modL2Src(MacAddress.valueOf("11:22:33:44:55:66"));
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod vlan id instructions.
+ */
+ @Test
+ public void modVlanIdInstructionTest() {
+ final L2ModificationInstruction.ModVlanIdInstruction instruction =
+ (L2ModificationInstruction.ModVlanIdInstruction)
+ Instructions.modVlanId(VlanId.vlanId((short) 12));
+
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod vlan pcp instructions.
+ */
+ @Test
+ public void modVlanPcpInstructionTest() {
+ final L2ModificationInstruction.ModVlanPcpInstruction instruction =
+ (L2ModificationInstruction.ModVlanPcpInstruction)
+ Instructions.modVlanPcp((byte) 9);
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod IPv4 src instructions.
+ */
+ @Test
+ public void modIPSrcInstructionTest() {
+ final Ip4Address ip = Ip4Address.valueOf("1.2.3.4");
+ final L3ModificationInstruction.ModIPInstruction instruction =
+ (L3ModificationInstruction.ModIPInstruction)
+ Instructions.modL3Src(ip);
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod IPv4 dst instructions.
+ */
+ @Test
+ public void modIPDstInstructionTest() {
+ final Ip4Address ip = Ip4Address.valueOf("1.2.3.4");
+ final L3ModificationInstruction.ModIPInstruction instruction =
+ (L3ModificationInstruction.ModIPInstruction)
+ Instructions.modL3Dst(ip);
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod IPv6 src instructions.
+ */
+ @Test
+ public void modIPv6SrcInstructionTest() {
+ final Ip6Address ip = Ip6Address.valueOf("1111::2222");
+ final L3ModificationInstruction.ModIPInstruction instruction =
+ (L3ModificationInstruction.ModIPInstruction)
+ Instructions.modL3IPv6Src(ip);
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod IPv6 dst instructions.
+ */
+ @Test
+ public void modIPv6DstInstructionTest() {
+ final Ip6Address ip = Ip6Address.valueOf("1111::2222");
+ final L3ModificationInstruction.ModIPInstruction instruction =
+ (L3ModificationInstruction.ModIPInstruction)
+ Instructions.modL3IPv6Dst(ip);
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod IPv6 flow label instructions.
+ */
+ @Test
+ public void modIPv6FlowLabelInstructionTest() {
+ final int flowLabel = 0xfffff;
+ final L3ModificationInstruction.ModIPv6FlowLabelInstruction instruction =
+ (L3ModificationInstruction.ModIPv6FlowLabelInstruction)
+ Instructions.modL3IPv6FlowLabel(flowLabel);
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+ /**
+ * Tests the encoding of mod MPLS label instructions.
+ */
+ @Test
+ public void modMplsLabelInstructionTest() {
+ final L2ModificationInstruction.ModMplsLabelInstruction instruction =
+ (L2ModificationInstruction.ModMplsLabelInstruction)
+ Instructions.modMplsLabel(MplsLabel.mplsLabel(99));
+ final ObjectNode instructionJson =
+ instructionCodec.encode(instruction, context);
+ assertThat(instructionJson, matchesInstruction(instruction));
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
new file mode 100644
index 00000000..72081e6c
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
@@ -0,0 +1,438 @@
+/*
+ * 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.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.flow.instructions.Instruction;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import static org.onosproject.net.flow.instructions.Instructions.*;
+import static org.onosproject.net.flow.instructions.L0ModificationInstruction.*;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.*;
+import static org.onosproject.net.flow.instructions.L3ModificationInstruction.*;
+
+/**
+ * Hamcrest matcher for instructions.
+ */
+public final class InstructionJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final Instruction instruction;
+
+ private InstructionJsonMatcher(Instruction instructionValue) {
+ instruction = instructionValue;
+ }
+
+ /**
+ * Matches the contents of a push header instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchPushHeaderInstruction(JsonNode instructionJson,
+ Description description) {
+ PushHeaderInstructions instructionToMatch =
+ (PushHeaderInstructions) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final JsonNode ethJson = instructionJson.get("ethernetType");
+ if (ethJson == null) {
+ description.appendText("ethernetType was not null");
+ return false;
+ }
+
+ if (instructionToMatch.ethernetType().toShort() != ethJson.asInt()) {
+ description.appendText("ethernetType was " + ethJson);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of an output instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchOutputInstruction(JsonNode instructionJson,
+ Description description) {
+ OutputInstruction instructionToMatch = (OutputInstruction) instruction;
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final long jsonPort = instructionJson.get("port").asLong();
+ if (instructionToMatch.port().toLong() != jsonPort) {
+ description.appendText("port was " + jsonPort);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod lambda instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModLambdaInstruction(JsonNode instructionJson,
+ Description description) {
+ ModLambdaInstruction instructionToMatch =
+ (ModLambdaInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final long jsonLambda = instructionJson.get("lambda").shortValue();
+ if (instructionToMatch.lambda() != jsonLambda) {
+ description.appendText("lambda was " + jsonLambda);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches teh contents of a mod OCh singal instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents matches, false otherwise
+ */
+ private boolean matchModOchSingalInstruction(JsonNode instructionJson,
+ Description description) {
+ ModOchSignalInstruction instructionToMatch =
+ (ModOchSignalInstruction) instruction;
+
+ String jsonSubType = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubType)) {
+ description.appendText("subtype was " + jsonSubType);
+ return false;
+ }
+
+ String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ String jsonGridType = instructionJson.get("gridType").textValue();
+ if (!instructionToMatch.lambda().gridType().name().equals(jsonGridType)) {
+ description.appendText("gridType was " + jsonGridType);
+ return false;
+ }
+
+ String jsonChannelSpacing = instructionJson.get("channelSpacing").textValue();
+ if (!instructionToMatch.lambda().channelSpacing().name().equals(jsonChannelSpacing)) {
+ description.appendText("channelSpacing was " + jsonChannelSpacing);
+ return false;
+ }
+
+ int jsonSpacingMultiplier = instructionJson.get("spacingMultiplier").intValue();
+ if (instructionToMatch.lambda().spacingMultiplier() != jsonSpacingMultiplier) {
+ description.appendText("spacingMultiplier was " + jsonSpacingMultiplier);
+ return false;
+ }
+
+ int jsonSlotGranularity = instructionJson.get("slotGranularity").intValue();
+ if (instructionToMatch.lambda().slotGranularity() != jsonSlotGranularity) {
+ description.appendText("slotGranularity was " + jsonSlotGranularity);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod Ethernet instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModEtherInstruction(JsonNode instructionJson,
+ Description description) {
+ ModEtherInstruction instructionToMatch =
+ (ModEtherInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final String jsonMac = instructionJson.get("mac").textValue();
+ final String mac = instructionToMatch.mac().toString();
+ if (!mac.equals(jsonMac)) {
+ description.appendText("mac was " + jsonMac);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod vlan id instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModVlanIdInstruction(JsonNode instructionJson,
+ Description description) {
+ ModVlanIdInstruction instructionToMatch =
+ (ModVlanIdInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final short jsonVlanId = instructionJson.get("vlanId").shortValue();
+ final short vlanId = instructionToMatch.vlanId().toShort();
+ if (jsonVlanId != vlanId) {
+ description.appendText("vlan id was " + jsonVlanId);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod vlan pcp instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModVlanPcpInstruction(JsonNode instructionJson,
+ Description description) {
+ ModVlanPcpInstruction instructionToMatch =
+ (ModVlanPcpInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final short jsonVlanPcp = instructionJson.get("vlanPcp").shortValue();
+ final short vlanId = instructionToMatch.vlanPcp();
+ if (jsonVlanPcp != vlanId) {
+ description.appendText("vlan pcp was " + jsonVlanPcp);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod ip instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModIpInstruction(JsonNode instructionJson,
+ Description description) {
+ ModIPInstruction instructionToMatch =
+ (ModIPInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final String jsonIp = instructionJson.get("ip").textValue();
+ final String ip = instructionToMatch.ip().toString();
+ if (!ip.equals(jsonIp)) {
+ description.appendText("ip was " + jsonIp);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod IPv6 Flow Label instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModIPv6FlowLabelInstruction(JsonNode instructionJson,
+ Description description) {
+ ModIPv6FlowLabelInstruction instructionToMatch =
+ (ModIPv6FlowLabelInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final int jsonFlowLabel = instructionJson.get("flowLabel").intValue();
+ final int flowLabel = instructionToMatch.flowLabel();
+ if (flowLabel != jsonFlowLabel) {
+ description.appendText("IPv6 flow label was " + jsonFlowLabel);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches the contents of a mod MPLS label instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchModMplsLabelInstruction(JsonNode instructionJson,
+ Description description) {
+ ModMplsLabelInstruction instructionToMatch =
+ (ModMplsLabelInstruction) instruction;
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ final int jsonLabel = instructionJson.get("label").intValue();
+ final int label = instructionToMatch.mplsLabel().toInt();
+ if (label != jsonLabel) {
+ description.appendText("MPLS label was " + jsonLabel);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonInstruction, Description description) {
+
+ // check type
+ final JsonNode jsonTypeNode = jsonInstruction.get("type");
+ final String jsonType = jsonTypeNode.textValue();
+ final String type = instruction.type().name();
+ if (!jsonType.equals(type)) {
+ description.appendText("type was " + type);
+ return false;
+ }
+
+ if (instruction instanceof PushHeaderInstructions) {
+ return matchPushHeaderInstruction(jsonInstruction, description);
+ } else if (instruction instanceof DropInstruction) {
+ return true;
+ } else if (instruction instanceof OutputInstruction) {
+ return matchOutputInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModLambdaInstruction) {
+ return matchModLambdaInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModOchSignalInstruction) {
+ return matchModOchSingalInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModEtherInstruction) {
+ return matchModEtherInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModVlanIdInstruction) {
+ return matchModVlanIdInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModVlanPcpInstruction) {
+ return matchModVlanPcpInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModIPInstruction) {
+ return matchModIpInstruction(jsonInstruction, description);
+ } else if (instruction instanceof ModIPv6FlowLabelInstruction) {
+ return matchModIPv6FlowLabelInstruction(jsonInstruction,
+ description);
+ } else if (instruction instanceof ModMplsLabelInstruction) {
+ return matchModMplsLabelInstruction(jsonInstruction, description);
+ }
+
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(instruction.toString());
+ }
+
+ /**
+ * Factory to allocate an instruction matcher.
+ *
+ * @param instruction instruction object we are looking for
+ * @return matcher
+ */
+ public static InstructionJsonMatcher matchesInstruction(Instruction instruction) {
+ return new InstructionJsonMatcher(instruction);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java
new file mode 100644
index 00000000..7cbce4d1
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.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.codec.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Duration;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.util.Bandwidth;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.onosproject.net.HostId;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.PortNumber;
+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.Criteria;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentServiceAdapter;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.AsymmetricPathConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.LambdaResource;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+
+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.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.IntentJsonMatcher.matchesIntent;
+import static org.onosproject.net.NetTestTools.did;
+import static org.onosproject.net.NetTestTools.hid;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
+
+/**
+ * Unit tests for the host to host intent class codec.
+ */
+public class IntentCodecTest extends AbstractIntentTest {
+
+ private final HostId id1 = hid("12:34:56:78:91:ab/1");
+ private final HostId id2 = hid("12:34:56:78:92:ab/1");
+ private final ApplicationId appId = new DefaultApplicationId(3, "test");
+ final TrafficSelector emptySelector =
+ DefaultTrafficSelector.emptySelector();
+ final TrafficTreatment emptyTreatment =
+ DefaultTrafficTreatment.emptyTreatment();
+ private final MockCodecContext context = new MockCodecContext();
+ final CoreService mockCoreService = createMock(CoreService.class);
+
+ @Before
+ public void setUpIntentService() {
+ final IntentService mockIntentService = new IntentServiceAdapter();
+ context.registerService(IntentService.class, mockIntentService);
+ context.registerService(CoreService.class, mockCoreService);
+ expect(mockCoreService.getAppId(appId.name()))
+ .andReturn(appId);
+ replay(mockCoreService);
+ }
+
+ /**
+ * Tests the encoding of a host to host intent.
+ */
+ @Test
+ public void hostToHostIntent() {
+ final HostToHostIntent intent =
+ HostToHostIntent.builder()
+ .appId(appId)
+ .one(id1)
+ .two(id2)
+ .build();
+
+ final JsonCodec<HostToHostIntent> intentCodec =
+ context.codec(HostToHostIntent.class);
+ assertThat(intentCodec, notNullValue());
+
+ final ObjectNode intentJson = intentCodec.encode(intent, context);
+ assertThat(intentJson, matchesIntent(intent));
+ }
+
+ /**
+ * Tests the encoding of a point to point intent.
+ */
+ @Test
+ public void pointToPointIntent() {
+ ConnectPoint ingress = NetTestTools.connectPoint("ingress", 1);
+ ConnectPoint egress = NetTestTools.connectPoint("egress", 2);
+
+ final PointToPointIntent intent =
+ PointToPointIntent.builder()
+ .appId(appId)
+ .selector(emptySelector)
+ .treatment(emptyTreatment)
+ .ingressPoint(ingress)
+ .egressPoint(egress).build();
+
+ final JsonCodec<PointToPointIntent> intentCodec =
+ context.codec(PointToPointIntent.class);
+ assertThat(intentCodec, notNullValue());
+
+ final ObjectNode intentJson = intentCodec.encode(intent, context);
+ assertThat(intentJson, matchesIntent(intent));
+ }
+
+ /**
+ * Tests the encoding of an intent with treatment, selector and constraints
+ * specified.
+ */
+ @Test
+ public void intentWithTreatmentSelectorAndConstraints() {
+ ConnectPoint ingress = NetTestTools.connectPoint("ingress", 1);
+ ConnectPoint egress = NetTestTools.connectPoint("egress", 2);
+ DeviceId did1 = did("device1");
+ DeviceId did2 = did("device2");
+ DeviceId did3 = did("device3");
+ Lambda ochSignal = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8);
+ final TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchIPProtocol((byte) 3)
+ .matchMplsLabel(MplsLabel.mplsLabel(4))
+ .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
+ .add(Criteria.matchLambda(ochSignal))
+ .matchEthDst(MacAddress.BROADCAST)
+ .matchIPDst(IpPrefix.valueOf("1.2.3.4/24"))
+ .build();
+ final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .add(Instructions.modL0Lambda(new IndexedLambda(33)))
+ .setMpls(MplsLabel.mplsLabel(44))
+ .setOutput(PortNumber.CONTROLLER)
+ .setEthDst(MacAddress.BROADCAST)
+ .build();
+
+ final List<Constraint> constraints =
+ ImmutableList.of(
+ new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(1.0))),
+ new LambdaConstraint(LambdaResource.valueOf(3)),
+ new AnnotationConstraint("key", 33.0),
+ new AsymmetricPathConstraint(),
+ new LatencyConstraint(Duration.ofSeconds(2)),
+ new ObstacleConstraint(did1, did2),
+ new WaypointConstraint(did3));
+
+ final PointToPointIntent intent =
+ PointToPointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(ingress)
+ .egressPoint(egress)
+ .constraints(constraints)
+ .build();
+
+
+ final JsonCodec<PointToPointIntent> intentCodec =
+ context.codec(PointToPointIntent.class);
+ assertThat(intentCodec, notNullValue());
+
+ final ObjectNode intentJson = intentCodec.encode(intent, context);
+ assertThat(intentJson, matchesIntent(intent));
+
+ }
+
+ /**
+ * Reads in a rule from the given resource and decodes it.
+ *
+ * @param resourceName resource to use to read the JSON for the rule
+ * @return decoded flow rule
+ * @throws IOException if processing the resource fails
+ */
+ private Intent getIntent(String resourceName, JsonCodec intentCodec) throws IOException {
+ InputStream jsonStream = FlowRuleCodecTest.class
+ .getResourceAsStream(resourceName);
+ JsonNode json = context.mapper().readTree(jsonStream);
+ assertThat(json, notNullValue());
+ Intent intent = (Intent) intentCodec.decode((ObjectNode) json, context);
+ assertThat(intent, notNullValue());
+ return intent;
+ }
+
+ /**
+ * Tests the point to point intent JSON codec.
+ *
+ * @throws IOException if JSON processing fails
+ */
+ @Test
+ public void decodePointToPointIntent() throws IOException {
+ JsonCodec<Intent> intentCodec = context.codec(Intent.class);
+ assertThat(intentCodec, notNullValue());
+
+ Intent intent = getIntent("PointToPointIntent.json", intentCodec);
+ assertThat(intent, notNullValue());
+ assertThat(intent, instanceOf(PointToPointIntent.class));
+
+ PointToPointIntent pointIntent = (PointToPointIntent) intent;
+ assertThat(pointIntent.priority(), is(55));
+ assertThat(pointIntent.ingressPoint().deviceId(), is(did("0000000000000001")));
+ assertThat(pointIntent.ingressPoint().port(), is(PortNumber.portNumber(1)));
+ assertThat(pointIntent.egressPoint().deviceId(), is(did("0000000000000007")));
+ assertThat(pointIntent.egressPoint().port(), is(PortNumber.portNumber(2)));
+
+ assertThat(pointIntent.constraints(), hasSize(1));
+
+ assertThat(pointIntent.selector(), notNullValue());
+ assertThat(pointIntent.selector().criteria(), hasSize(1));
+ Criterion criterion1 = pointIntent.selector().criteria().iterator().next();
+ assertThat(criterion1, instanceOf(EthCriterion.class));
+ EthCriterion ethCriterion = (EthCriterion) criterion1;
+ assertThat(ethCriterion.mac().toString(), is("11:22:33:44:55:66"));
+ assertThat(ethCriterion.type().name(), is("ETH_DST"));
+
+ assertThat(pointIntent.treatment(), notNullValue());
+ assertThat(pointIntent.treatment().allInstructions(), hasSize(1));
+ Instruction instruction1 = pointIntent.treatment().allInstructions().iterator().next();
+ assertThat(instruction1, instanceOf(ModEtherInstruction.class));
+ ModEtherInstruction ethInstruction = (ModEtherInstruction) instruction1;
+ assertThat(ethInstruction.mac().toString(), is("22:33:44:55:66:77"));
+ assertThat(ethInstruction.type().toString(), is("L2MODIFICATION"));
+ assertThat(ethInstruction.subtype().toString(), is("ETH_SRC"));
+ }
+
+ /**
+ * Tests the host to host intent JSON codec.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void decodeHostToHostIntent() throws IOException {
+ JsonCodec<Intent> intentCodec = context.codec(Intent.class);
+ assertThat(intentCodec, notNullValue());
+
+ Intent intent = getIntent("HostToHostIntent.json", intentCodec);
+ assertThat(intent, notNullValue());
+ assertThat(intent, instanceOf(HostToHostIntent.class));
+
+ HostToHostIntent hostIntent = (HostToHostIntent) intent;
+ assertThat(hostIntent.priority(), is(7));
+ assertThat(hostIntent.constraints(), hasSize(1));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java
new file mode 100644
index 00000000..e485a5fa
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java
@@ -0,0 +1,512 @@
+/*
+ * 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.codec.impl;
+
+import java.util.List;
+import java.util.Set;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetworkResource;
+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.intent.ConnectivityIntent;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.LinkTypeConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher to check that an intent representation in JSON matches
+ * the actual intent.
+ */
+public final class IntentJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final Intent intent;
+
+ /**
+ * Constructor is private, use factory method.
+ *
+ * @param intentValue the intent object to compare against
+ */
+ private IntentJsonMatcher(Intent intentValue) {
+ intent = intentValue;
+ }
+
+ /**
+ * Matches the JSON representation of a host to host intent.
+ *
+ * @param jsonIntent JSON representation of the intent
+ * @param description Description object used for recording errors
+ * @return true if the JSON matches the intent, false otherwise
+ */
+ private boolean matchHostToHostIntent(JsonNode jsonIntent, Description description) {
+ final HostToHostIntent hostToHostIntent = (HostToHostIntent) intent;
+
+ // check host one
+ final String host1 = hostToHostIntent.one().toString();
+ final String jsonHost1 = jsonIntent.get("one").asText();
+ if (!host1.equals(jsonHost1)) {
+ description.appendText("host one was " + jsonHost1);
+ return false;
+ }
+
+ // check host 2
+ final String host2 = hostToHostIntent.two().toString();
+ final String jsonHost2 = jsonIntent.get("two").asText();
+ if (!host2.equals(jsonHost2)) {
+ description.appendText("host two was " + jsonHost2);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches the JSON representation of a point to point intent.
+ *
+ * @param jsonIntent JSON representation of the intent
+ * @param description Description object used for recording errors
+ * @return true if the JSON matches the intent, false otherwise
+ */
+ private boolean matchPointToPointIntent(JsonNode jsonIntent, Description description) {
+ final PointToPointIntent pointToPointIntent = (PointToPointIntent) intent;
+
+ // check ingress connection
+ final ConnectPoint ingress = pointToPointIntent.ingressPoint();
+ final ConnectPointJsonMatcher ingressMatcher =
+ ConnectPointJsonMatcher.matchesConnectPoint(ingress);
+ final JsonNode jsonIngress = jsonIntent.get("ingressPoint");
+ final boolean ingressMatches =
+ ingressMatcher.matchesSafely(jsonIngress, description);
+
+ if (!ingressMatches) {
+ description.appendText("ingress was " + jsonIngress);
+ return false;
+ }
+
+ // check egress connection
+ final ConnectPoint egress = pointToPointIntent.egressPoint();
+ final ConnectPointJsonMatcher egressMatcher =
+ ConnectPointJsonMatcher.matchesConnectPoint(egress);
+ final JsonNode jsonEgress = jsonIntent.get("egressPoint");
+ final boolean egressMatches =
+ egressMatcher.matchesSafely(jsonEgress, description);
+
+ if (!egressMatches) {
+ description.appendText("egress was " + jsonEgress);
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Matches a bandwidth constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param bandwidthConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchBandwidthConstraint(BandwidthConstraint bandwidthConstraint,
+ JsonNode constraintJson) {
+ final JsonNode bandwidthJson = constraintJson.get("bandwidth");
+ return bandwidthJson != null
+ && constraintJson.get("bandwidth").asDouble()
+ == bandwidthConstraint.bandwidth().toDouble();
+ }
+
+ /**
+ * Matches a lamdba constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param lambdaConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchLambdaConstraint(LambdaConstraint lambdaConstraint,
+ JsonNode constraintJson) {
+ final JsonNode lambdaJson = constraintJson.get("lambda");
+ return lambdaJson != null
+ && constraintJson.get("lambda").asInt()
+ == lambdaConstraint.lambda().toInt();
+ }
+
+ /**
+ * Matches a link type constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param linkTypeConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchLinkTypeConstraint(LinkTypeConstraint linkTypeConstraint,
+ JsonNode constraintJson) {
+ final JsonNode inclusiveJson = constraintJson.get("inclusive");
+ final JsonNode typesJson = constraintJson.get("types");
+
+ if (typesJson.size() != linkTypeConstraint.types().size()) {
+ return false;
+ }
+
+ int foundType = 0;
+ for (Link.Type type : linkTypeConstraint.types()) {
+ for (int jsonIndex = 0; jsonIndex < typesJson.size(); jsonIndex++) {
+ if (type.name().equals(typesJson.get(jsonIndex).asText())) {
+ foundType++;
+ break;
+ }
+ }
+ }
+ return (inclusiveJson != null &&
+ inclusiveJson.asBoolean() == linkTypeConstraint.isInclusive()) &&
+ foundType == typesJson.size();
+ }
+
+ /**
+ * Matches an annotation constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param annotationConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchAnnotationConstraint(AnnotationConstraint annotationConstraint,
+ JsonNode constraintJson) {
+ final JsonNode keyJson = constraintJson.get("key");
+ final JsonNode thresholdJson = constraintJson.get("threshold");
+ return (keyJson != null
+ && keyJson.asText().equals(annotationConstraint.key())) &&
+ (thresholdJson != null
+ && thresholdJson.asDouble() == annotationConstraint.threshold());
+ }
+
+ /**
+ * Matches a latency constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param latencyConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchLatencyConstraint(LatencyConstraint latencyConstraint,
+ JsonNode constraintJson) {
+ final JsonNode latencyJson = constraintJson.get("latencyMillis");
+ return (latencyJson != null
+ && latencyJson.asInt() == latencyConstraint.latency().toMillis());
+ }
+
+ /**
+ * Matches an obstacle constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param obstacleConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchObstacleConstraint(ObstacleConstraint obstacleConstraint,
+ JsonNode constraintJson) {
+ final JsonNode obstaclesJson = constraintJson.get("obstacles");
+
+ if (obstaclesJson.size() != obstacleConstraint.obstacles().size()) {
+ return false;
+ }
+
+ for (int obstaclesIndex = 0; obstaclesIndex < obstaclesJson.size();
+ obstaclesIndex++) {
+ boolean obstacleFound = false;
+ final String obstacleJson = obstaclesJson.get(obstaclesIndex)
+ .asText();
+ for (DeviceId obstacle : obstacleConstraint.obstacles()) {
+ if (obstacle.toString().equals(obstacleJson)) {
+ obstacleFound = true;
+ }
+ }
+ if (!obstacleFound) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Matches a waypoint constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param waypointConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchWaypointConstraint(WaypointConstraint waypointConstraint,
+ JsonNode constraintJson) {
+ final JsonNode waypointsJson = constraintJson.get("waypoints");
+
+ if (waypointsJson.size() != waypointConstraint.waypoints().size()) {
+ return false;
+ }
+
+ for (int waypointsIndex = 0; waypointsIndex < waypointsJson.size();
+ waypointsIndex++) {
+ boolean waypointFound = false;
+ final String waypointJson = waypointsJson.get(waypointsIndex)
+ .asText();
+ for (DeviceId waypoint : waypointConstraint.waypoints()) {
+ if (waypoint.toString().equals(waypointJson)) {
+ waypointFound = true;
+ }
+ }
+ if (!waypointFound) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Matches a constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param constraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchConstraint(Constraint constraint, JsonNode constraintJson) {
+ final JsonNode typeJson = constraintJson.get("type");
+ if (!typeJson.asText().equals(constraint.getClass().getSimpleName())) {
+ return false;
+ }
+ if (constraint instanceof BandwidthConstraint) {
+ return matchBandwidthConstraint((BandwidthConstraint) constraint,
+ constraintJson);
+ } else if (constraint instanceof LambdaConstraint) {
+ return matchLambdaConstraint((LambdaConstraint) constraint,
+ constraintJson);
+ } else if (constraint instanceof LinkTypeConstraint) {
+ return matchLinkTypeConstraint((LinkTypeConstraint) constraint,
+ constraintJson);
+ } else if (constraint instanceof AnnotationConstraint) {
+ return matchAnnotationConstraint((AnnotationConstraint) constraint,
+ constraintJson);
+ } else if (constraint instanceof LatencyConstraint) {
+ return matchLatencyConstraint((LatencyConstraint) constraint,
+ constraintJson);
+ } else if (constraint instanceof ObstacleConstraint) {
+ return matchObstacleConstraint((ObstacleConstraint) constraint,
+ constraintJson);
+ } else if (constraint instanceof WaypointConstraint) {
+ return matchWaypointConstraint((WaypointConstraint) constraint,
+ constraintJson);
+ }
+ return true;
+ }
+
+ /**
+ * Matches the JSON representation of a connectivity intent. Calls the
+ * matcher for the connectivity intent subtype.
+ *
+ * @param jsonIntent JSON representation of the intent
+ * @param description Description object used for recording errors
+ * @return true if the JSON matches the intent, false otherwise
+ */
+ private boolean matchConnectivityIntent(JsonNode jsonIntent, Description description) {
+ final ConnectivityIntent connectivityIntent = (ConnectivityIntent) intent;
+
+ // check selector
+ final JsonNode jsonSelector = jsonIntent.get("selector");
+ final TrafficSelector selector = connectivityIntent.selector();
+ final Set<Criterion> criteria = selector.criteria();
+ final JsonNode jsonCriteria = jsonSelector.get("criteria");
+ if (jsonCriteria.size() != criteria.size()) {
+ description.appendText("size of criteria array is "
+ + Integer.toString(jsonCriteria.size()));
+ return false;
+ }
+
+ for (Criterion criterion : criteria) {
+ boolean criterionFound = false;
+ for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) {
+ final CriterionJsonMatcher criterionMatcher =
+ CriterionJsonMatcher.matchesCriterion(criterion);
+ if (criterionMatcher.matches(jsonCriteria.get(criterionIndex))) {
+ criterionFound = true;
+ break;
+ }
+ }
+ if (!criterionFound) {
+ description.appendText("criterion not found " + criterion.toString());
+ return false;
+ }
+ }
+
+ // check treatment
+ final JsonNode jsonTreatment = jsonIntent.get("treatment");
+ final TrafficTreatment treatment = connectivityIntent.treatment();
+ final List<Instruction> instructions = treatment.immediate();
+ final JsonNode jsonInstructions = jsonTreatment.get("instructions");
+ if (jsonInstructions.size() != instructions.size()) {
+ description.appendText("size of instructions array is "
+ + Integer.toString(jsonInstructions.size()));
+ return false;
+ }
+
+ for (Instruction instruction : instructions) {
+ boolean instructionFound = false;
+ for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) {
+ final InstructionJsonMatcher instructionMatcher =
+ InstructionJsonMatcher.matchesInstruction(instruction);
+ if (instructionMatcher.matches(jsonInstructions.get(instructionIndex))) {
+ instructionFound = true;
+ break;
+ }
+ }
+ if (!instructionFound) {
+ description.appendText("instruction not found " + instruction.toString());
+ return false;
+ }
+ }
+
+ // Check constraints
+ final JsonNode jsonConstraints = jsonIntent.get("constraints");
+ if (connectivityIntent.constraints() != null) {
+ if (connectivityIntent.constraints().size() != jsonConstraints.size()) {
+ description.appendText("constraints array size was "
+ + Integer.toString(jsonConstraints.size()));
+ return false;
+ }
+ for (final Constraint constraint : connectivityIntent.constraints()) {
+ boolean constraintFound = false;
+ for (int constraintIndex = 0; constraintIndex < jsonConstraints.size();
+ constraintIndex++) {
+ final JsonNode value = jsonConstraints.get(constraintIndex);
+ if (matchConstraint(constraint, value)) {
+ constraintFound = true;
+ }
+ }
+ if (!constraintFound) {
+ final String constraintString = constraint.toString();
+ description.appendText("constraint missing " + constraintString);
+ return false;
+ }
+ }
+ } else if (jsonConstraints.size() != 0) {
+ description.appendText("constraint array not empty");
+ return false;
+ }
+
+ if (connectivityIntent instanceof HostToHostIntent) {
+ return matchHostToHostIntent(jsonIntent, description);
+ } else if (connectivityIntent instanceof PointToPointIntent) {
+ return matchPointToPointIntent(jsonIntent, description);
+ } else {
+ description.appendText("class of connectivity intent is unknown");
+ return false;
+ }
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonIntent, Description description) {
+ // check id
+ final String jsonId = jsonIntent.get("id").asText();
+ final String id = intent.id().toString();
+ if (!jsonId.equals(id)) {
+ description.appendText("id was " + jsonId);
+ return false;
+ }
+
+ // check application id
+ final JsonNode jsonAppIdNode = jsonIntent.get("appId");
+
+ final String jsonAppId = jsonAppIdNode.asText();
+ final String appId = intent.appId().name();
+ if (!jsonAppId.equals(appId)) {
+ description.appendText("appId was " + jsonAppId);
+ return false;
+ }
+
+ // check intent type
+ final String jsonType = jsonIntent.get("type").asText();
+ final String type = intent.getClass().getSimpleName();
+ if (!jsonType.equals(type)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ // check resources array
+ final JsonNode jsonResources = jsonIntent.get("resources");
+ if (intent.resources() != null) {
+ if (intent.resources().size() != jsonResources.size()) {
+ description.appendText("resources array size was "
+ + Integer.toString(jsonResources.size()));
+ return false;
+ }
+ for (final NetworkResource resource : intent.resources()) {
+ boolean resourceFound = false;
+ final String resourceString = resource.toString();
+ for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) {
+ final JsonNode value = jsonResources.get(resourceIndex);
+ if (value.asText().equals(resourceString)) {
+ resourceFound = true;
+ }
+ }
+ if (!resourceFound) {
+ description.appendText("resource missing " + resourceString);
+ return false;
+ }
+ }
+ } else if (jsonResources.size() != 0) {
+ description.appendText("resources array empty");
+ return false;
+ }
+
+ if (intent instanceof ConnectivityIntent) {
+ return matchConnectivityIntent(jsonIntent, description);
+ } else {
+ description.appendText("class of intent is unknown");
+ return false;
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(intent.toString());
+ }
+
+ /**
+ * Factory to allocate an intent matcher.
+ *
+ * @param intent intent object we are looking for
+ * @return matcher
+ */
+ public static IntentJsonMatcher matchesIntent(Intent intent) {
+ return new IntentJsonMatcher(intent);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java
new file mode 100644
index 00000000..67c2f47f
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+
+import org.onlab.packet.ChassisId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.provider.ProviderId;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * JsonCodec test utilities.
+ */
+public abstract class JsonCodecUtils {
+
+ /**
+ * Checks if given Object can be encoded to JSON and back.
+ *
+ * @param context CodecContext
+ * @param codec JsonCodec
+ * @param pojoIn Java Object to encode.
+ * Object is expected to have #equals implemented.
+ */
+ public static <T> void assertJsonEncodable(final CodecContext context,
+ final JsonCodec<T> codec,
+ final T pojoIn) {
+ final ObjectNode json = codec.encode(pojoIn, context);
+
+ assertThat(json, is(notNullValue()));
+
+ final T pojoOut = codec.decode(json, context);
+ assertThat(pojoOut, is(notNullValue()));
+
+ assertEquals(pojoIn, pojoOut);
+ }
+
+ static final ProviderId PID = new ProviderId("of", "foo");
+ static final ProviderId PIDA = new ProviderId("of", "bar", true);
+ static final DeviceId DID1 = deviceId("of:foo");
+ static final DeviceId DID2 = deviceId("of:bar");
+ static final String MFR = "whitebox";
+ static final String HW = "1.1.x";
+ static final String SW1 = "3.8.1";
+ static final String SW2 = "3.9.5";
+ static final String SN = "43311-12345";
+ static final ChassisId CID = new ChassisId();
+ static final PortNumber P1 = PortNumber.portNumber(1);
+ static final PortNumber P2 = PortNumber.portNumber(2);
+ static final PortNumber P3 = PortNumber.portNumber(3);
+ static final SparseAnnotations A1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .build();
+ static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+ static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java
new file mode 100644
index 00000000..c44b0eb1
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+
+/**
+ * Unit test for LinkCodec.
+ */
+public class LinkCodecTest {
+
+ private final Link link = new DefaultLink(JsonCodecUtils.PID,
+ JsonCodecUtils.CP1,
+ JsonCodecUtils.CP2,
+ Link.Type.DIRECT,
+ Link.State.ACTIVE,
+ false,
+ JsonCodecUtils.A1);
+
+ @Test
+ public void linkCodecTest() {
+ final MockCodecContext context = new MockCodecContext();
+ context.registerService(DeviceService.class, new DeviceServiceAdapter());
+ final JsonCodec<Link> codec = context.codec(Link.class);
+ assertThat(codec, is(notNullValue()));
+ final Link pojoIn = link;
+
+ assertJsonEncodable(context, codec, pojoIn);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java
new file mode 100644
index 00000000..4cb2916e
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.codec.impl;
+
+import org.junit.Test;
+import org.onosproject.net.statistic.DefaultLoad;
+import org.onosproject.net.statistic.Load;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Unit tests for Load codec.
+ */
+public class LoadCodecTest {
+
+ /**
+ * Tests encoding of a Load object.
+ */
+ @Test
+ public void testLoadEncode() {
+ final long startTime = System.currentTimeMillis();
+ final Load load = new DefaultLoad(20, 10, 1);
+ final JsonNode node = new LoadCodec()
+ .encode(load, new MockCodecContext());
+ assertThat(node.get("valid").asBoolean(), is(true));
+ assertThat(node.get("latest").asLong(), is(20L));
+ assertThat(node.get("rate").asLong(), is(10L));
+ assertThat(node.get("time").asLong(), greaterThanOrEqualTo(startTime));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
new file mode 100644
index 00000000..6a9b6708
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.codec.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Mock codec context for use in codec unit tests.
+ */
+public class MockCodecContext implements CodecContext {
+
+ private final ObjectMapper mapper = new ObjectMapper();
+ private final CodecManager manager = new CodecManager();
+ private final Map<Class<? extends Object>, Object> services = new HashMap<>();
+
+ /**
+ * Constructs a new mock codec context.
+ */
+ public MockCodecContext() {
+ manager.activate();
+ }
+
+ @Override
+ public ObjectMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> JsonCodec<T> codec(Class<T> entityClass) {
+ return manager.getCodec(entityClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T getService(Class<T> serviceClass) {
+ return (T) services.get(serviceClass);
+ }
+
+ // for registering mock services
+ public <T> void registerService(Class<T> serviceClass, T impl) {
+ services.put(serviceClass, impl);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java
new file mode 100644
index 00000000..f3f7d920
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+
+/**
+ * Unit test for PortCodec.
+ */
+public class PortCodecTest {
+
+
+
+ private final Device device = new DefaultDevice(JsonCodecUtils.PID,
+ JsonCodecUtils.DID1,
+ Device.Type.SWITCH,
+ JsonCodecUtils.MFR,
+ JsonCodecUtils.HW,
+ JsonCodecUtils.SW1,
+ JsonCodecUtils.SN,
+ JsonCodecUtils.CID,
+ JsonCodecUtils.A1);
+
+ private final Port port = new DefaultPort(device,
+ JsonCodecUtils.P1,
+ true,
+ JsonCodecUtils.A1);
+
+ @Test
+ public void portCodecTest() {
+ final MockCodecContext context = new MockCodecContext();
+ context.registerService(DeviceService.class, new DeviceServiceAdapter());
+ final JsonCodec<Port> codec = context.codec(Port.class);
+ assertThat(codec, is(notNullValue()));
+ final Port pojoIn = port;
+
+ assertJsonEncodable(context, codec, pojoIn);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java
new file mode 100644
index 00000000..4d435cfe
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.common;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.ClusterId;
+import org.onosproject.net.topology.DefaultGraphDescription;
+import org.onosproject.net.topology.GraphDescription;
+import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.TopologyCluster;
+
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.junit.Assert.*;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the default topology implementation.
+ */
+public class DefaultTopologyTest {
+
+ public static final ProviderId PID = new ProviderId("of", "foo.bar");
+
+ public static final DeviceId D1 = deviceId("of:1");
+ public static final DeviceId D2 = deviceId("of:2");
+ public static final DeviceId D3 = deviceId("of:3");
+ public static final DeviceId D4 = deviceId("of:4");
+ public static final DeviceId D5 = deviceId("of:5");
+
+ public static final PortNumber P1 = portNumber(1);
+ public static final PortNumber P2 = portNumber(2);
+
+ public static final LinkWeight WEIGHT = edge ->
+ edge.src().deviceId().equals(D4) || edge.dst().deviceId().equals(D4)
+ ? 2.0 : 1.0;
+
+ private DefaultTopology dt;
+
+ @Before
+ public void setUp() {
+ long now = System.currentTimeMillis();
+ Set<Device> devices = of(device("1"), device("2"),
+ device("3"), device("4"),
+ device("5"));
+ Set<Link> links = of(link("1", 1, "2", 1), link("2", 1, "1", 1),
+ link("3", 2, "2", 2), link("2", 2, "3", 2),
+ link("1", 3, "4", 3), link("4", 3, "1", 3),
+ link("3", 4, "4", 4), link("4", 4, "3", 4));
+ GraphDescription graphDescription =
+ new DefaultGraphDescription(now, devices, links);
+
+ dt = new DefaultTopology(PID, graphDescription);
+ assertEquals("incorrect supplier", PID, dt.providerId());
+ assertEquals("incorrect time", now, dt.time());
+ assertEquals("incorrect device count", 5, dt.deviceCount());
+ assertEquals("incorrect link count", 8, dt.linkCount());
+ assertEquals("incorrect cluster count", 2, dt.clusterCount());
+ assertEquals("incorrect broadcast set size", 6,
+ dt.broadcastSetSize(ClusterId.clusterId(0)));
+ }
+
+ @Test
+ public void pathRelated() {
+ Set<Path> paths = dt.getPaths(D1, D2);
+ assertEquals("incorrect path count", 1, paths.size());
+
+ paths = dt.getPaths(D1, D3);
+ assertEquals("incorrect path count", 2, paths.size());
+
+ paths = dt.getPaths(D1, D5);
+ assertTrue("no paths expected", paths.isEmpty());
+
+ paths = dt.getPaths(D1, D3, WEIGHT);
+ assertEquals("incorrect path count", 1, paths.size());
+ }
+
+ @Test
+ public void pointRelated() {
+ assertTrue("should be infrastructure point",
+ dt.isInfrastructure(new ConnectPoint(D1, P1)));
+ assertFalse("should not be infrastructure point",
+ dt.isInfrastructure(new ConnectPoint(D1, P2)));
+ }
+
+ @Test
+ public void clusterRelated() {
+ Set<TopologyCluster> clusters = dt.getClusters();
+ assertEquals("incorrect cluster count", 2, clusters.size());
+
+ TopologyCluster c = dt.getCluster(D1);
+ Set<DeviceId> devs = dt.getClusterDevices(c);
+ assertEquals("incorrect cluster device count", 4, devs.size());
+ assertTrue("cluster should contain D2", devs.contains(D2));
+ assertFalse("cluster should not contain D5", devs.contains(D5));
+ }
+
+ // Short-hand for creating a link.
+ public static Link link(String src, int sp, String dst, int dp) {
+ return new DefaultLink(PID, new ConnectPoint(did(src), portNumber(sp)),
+ new ConnectPoint(did(dst), portNumber(dp)),
+ Link.Type.DIRECT);
+ }
+
+ // Crates a new device with the specified id
+ public static Device device(String id) {
+ return new DefaultDevice(PID, did(id), Device.Type.SWITCH,
+ "mfg", "1.0", "1.1", "1234", new ChassisId());
+ }
+
+ // Short-hand for producing a device id from a string
+ public static DeviceId did(String id) {
+ return deviceId("of:" + id);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java
new file mode 100644
index 00000000..97012c4e
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.common.app;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.util.Tools;
+import org.onosproject.app.ApplicationDescription;
+import org.onosproject.app.ApplicationException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+
+/**
+ * Suite of tests for the application archive utility.
+ */
+public class ApplicationArchiveTest {
+
+ static final File STORE = Files.createTempDir();
+
+ private ApplicationArchive aar = new ApplicationArchive();
+
+ @Before
+ public void setUp() {
+ aar.setRootPath(STORE.getAbsolutePath());
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (STORE.exists()) {
+ Tools.removeDirectory(STORE);
+ }
+ }
+
+ private void validate(ApplicationDescription app) {
+ assertEquals("incorrect name", APP_NAME, app.name());
+ assertEquals("incorrect version", VER, app.version());
+ assertEquals("incorrect origin", ORIGIN, app.origin());
+ assertEquals("incorrect role", ROLE, app.role());
+
+ assertEquals("incorrect description", DESC, app.description());
+ assertEquals("incorrect features URI", FURL, app.featuresRepo().get());
+ assertEquals("incorrect permissions", PERMS, app.permissions());
+ assertEquals("incorrect features", FEATURES, app.features());
+ }
+
+ @Test
+ public void saveZippedApp() throws IOException {
+ InputStream stream = getClass().getResourceAsStream("app.zip");
+ ApplicationDescription app = aar.saveApplication(stream);
+ validate(app);
+ stream.close();
+ }
+
+ @Test
+ public void savePlainApp() throws IOException {
+ InputStream stream = getClass().getResourceAsStream("app.xml");
+ ApplicationDescription app = aar.saveApplication(stream);
+ validate(app);
+ stream.close();
+ }
+
+ @Test
+ public void loadApp() throws IOException {
+ saveZippedApp();
+ ApplicationDescription app = aar.getApplicationDescription(APP_NAME);
+ validate(app);
+ }
+
+ @Test
+ public void getAppNames() throws IOException {
+ saveZippedApp();
+ Set<String> names = aar.getApplicationNames();
+ assertEquals("incorrect names", ImmutableSet.of(APP_NAME), names);
+ }
+
+ @Test
+ public void purgeApp() throws IOException {
+ saveZippedApp();
+ aar.purgeApplication(APP_NAME);
+ assertEquals("incorrect names", ImmutableSet.<String>of(),
+ aar.getApplicationNames());
+ }
+
+ @Test
+ public void getAppZipStream() throws IOException {
+ saveZippedApp();
+ InputStream stream = aar.getApplicationInputStream(APP_NAME);
+ byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.zip"));
+ byte[] loaded = ByteStreams.toByteArray(stream);
+ assertArrayEquals("incorrect stream", orig, loaded);
+ stream.close();
+ }
+
+ @Test
+ public void getAppXmlStream() throws IOException {
+ savePlainApp();
+ InputStream stream = aar.getApplicationInputStream(APP_NAME);
+ byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.xml"));
+ byte[] loaded = ByteStreams.toByteArray(stream);
+ assertArrayEquals("incorrect stream", orig, loaded);
+ stream.close();
+ }
+
+ @Test
+ public void active() throws IOException {
+ savePlainApp();
+ assertFalse("should not be active", aar.isActive(APP_NAME));
+ aar.setActive(APP_NAME);
+ assertTrue("should not be active", aar.isActive(APP_NAME));
+ aar.clearActive(APP_NAME);
+ assertFalse("should not be active", aar.isActive(APP_NAME));
+ }
+
+ @Test(expected = ApplicationException.class)
+ public void getBadAppDesc() throws IOException {
+ aar.getApplicationDescription("org.foo.BAD");
+ }
+
+ @Test(expected = ApplicationException.class)
+ public void getBadAppStream() throws IOException {
+ aar.getApplicationInputStream("org.foo.BAD");
+ }
+
+ @Test(expected = ApplicationException.class)
+ public void setBadActive() throws IOException {
+ aar.setActive("org.foo.BAD");
+ }
+
+ @Test // (expected = ApplicationException.class)
+ public void purgeBadApp() throws IOException {
+ aar.purgeApplication("org.foo.BAD");
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java
new file mode 100644
index 00000000..4ea371a0
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java
@@ -0,0 +1,48 @@
+/*
+ * 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.common.event.impl;
+
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Implements event delivery system that delivers events synchronously, or
+ * in-line with the post method invocation.
+ */
+public class TestEventDispatcher extends DefaultEventSinkRegistry
+ implements EventDeliveryService {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public synchronized void post(Event event) {
+ EventSink sink = getSink(event.getClass());
+ checkState(sink != null, "No sink for event %s", event);
+ sink.process(event);
+ }
+
+ @Override
+ public void setDispatchTimeLimit(long millis) {
+ }
+
+ @Override
+ public long getDispatchTimeLimit() {
+ return 0;
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java
new file mode 100644
index 00000000..00d6c9d2
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java
@@ -0,0 +1,55 @@
+/*
+ * 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.store.trivial;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * Key for filing pre-computed paths between source and destination devices.
+ */
+class PathKey {
+ private final DeviceId src;
+ private final DeviceId dst;
+
+ /**
+ * Creates a path key from the given source/dest pair.
+ * @param src source device
+ * @param dst destination device
+ */
+ PathKey(DeviceId src, DeviceId dst) {
+ this.src = src;
+ this.dst = dst;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(src, dst);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PathKey) {
+ final PathKey other = (PathKey) obj;
+ return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java
new file mode 100644
index 00000000..6e6b9587
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java
@@ -0,0 +1,70 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.DefaultApplicationId;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Simple implementation of the application ID registry using in-memory
+ * structures.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleApplicationIdStore implements ApplicationIdStore {
+
+ private static final AtomicInteger ID_DISPENSER = new AtomicInteger(1);
+
+ private final Map<Short, DefaultApplicationId> appIds = new ConcurrentHashMap<>();
+ private final Map<String, DefaultApplicationId> appIdsByName = new ConcurrentHashMap<>();
+
+ @Override
+ public Set<ApplicationId> getAppIds() {
+ return ImmutableSet.<ApplicationId>copyOf(appIds.values());
+ }
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ return appIds.get(id);
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ return appIdsByName.get(name);
+ }
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ DefaultApplicationId appId = appIdsByName.get(name);
+ if (appId == null) {
+ short id = (short) ID_DISPENSER.getAndIncrement();
+ appId = new DefaultApplicationId(id, name);
+ appIds.put(id, appId);
+ appIdsByName.put(name, appId);
+ }
+ return appId;
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
new file mode 100644
index 00000000..ea9a773e
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
@@ -0,0 +1,170 @@
+/*
+ * 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.store.trivial;
+
+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.app.ApplicationDescription;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStore;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.security.Permission;
+import org.slf4j.Logger;
+
+import java.io.InputStream;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.app.ApplicationState.ACTIVE;
+import static org.onosproject.app.ApplicationState.INSTALLED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of network control applications.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleApplicationStore extends ApplicationArchive implements ApplicationStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // App inventory & states
+ private final ConcurrentMap<ApplicationId, DefaultApplication> apps = new ConcurrentHashMap<>();
+ private final ConcurrentMap<ApplicationId, ApplicationState> states = new ConcurrentHashMap<>();
+ private final ConcurrentMap<ApplicationId, Set<Permission>> permissions = new ConcurrentHashMap<>();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationIdStore idStore;
+
+ @Activate
+ public void activate() {
+ loadFromDisk();
+ log.info("Started");
+ }
+
+ private void loadFromDisk() {
+ for (String name : getApplicationNames()) {
+ ApplicationId appId = idStore.registerApplication(name);
+ ApplicationDescription appDesc = getApplicationDescription(name);
+ DefaultApplication app =
+ new DefaultApplication(appId, appDesc.version(),
+ appDesc.description(), appDesc.origin(),
+ appDesc.role(), appDesc.permissions(),
+ appDesc.featuresRepo(), appDesc.features());
+ apps.put(appId, app);
+ states.put(appId, isActive(name) ? INSTALLED : ACTIVE);
+ // load app permissions
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ apps.clear();
+ states.clear();
+ permissions.clear();
+ log.info("Stopped");
+ }
+
+ @Override
+ public Set<Application> getApplications() {
+ return ImmutableSet.copyOf(apps.values());
+ }
+
+ @Override
+ public ApplicationId getId(String name) {
+ return idStore.getAppId(name);
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ return apps.get(appId);
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ return states.get(appId);
+ }
+
+ @Override
+ public Application create(InputStream appDescStream) {
+ ApplicationDescription appDesc = saveApplication(appDescStream);
+ ApplicationId appId = idStore.registerApplication(appDesc.name());
+ DefaultApplication app =
+ new DefaultApplication(appId, appDesc.version(), appDesc.description(),
+ appDesc.origin(), appDesc.role(), appDesc.permissions(),
+ appDesc.featuresRepo(), appDesc.features());
+ apps.put(appId, app);
+ states.put(appId, INSTALLED);
+ delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
+ return app;
+ }
+
+ @Override
+ public void remove(ApplicationId appId) {
+ Application app = apps.remove(appId);
+ if (app != null) {
+ states.remove(appId);
+ delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app));
+ purgeApplication(app.id().name());
+ }
+ }
+
+ @Override
+ public void activate(ApplicationId appId) {
+ Application app = apps.get(appId);
+ if (app != null) {
+ setActive(appId.name());
+ states.put(appId, ACTIVE);
+ delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
+ }
+ }
+
+ @Override
+ public void deactivate(ApplicationId appId) {
+ Application app = apps.get(appId);
+ if (app != null) {
+ clearActive(appId.name());
+ states.put(appId, INSTALLED);
+ delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
+ }
+ }
+
+ @Override
+ public Set<Permission> getPermissions(ApplicationId appId) {
+ return permissions.get(appId);
+ }
+
+ @Override
+ public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+ Application app = getApplication(appId);
+ if (app != null) {
+ this.permissions.put(appId, permissions);
+ delegate.notify(new ApplicationEvent(APP_PERMISSIONS_CHANGED, app));
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java
new file mode 100644
index 00000000..a1c7da37
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.util.Tools;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStoreAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.security.AppPermission;
+import org.onosproject.security.Permission;
+
+import java.io.File;
+import java.io.IOException;
+
+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;
+
+/**
+ * Test of the trivial application store implementation.
+ */
+public class SimpleApplicationStoreTest {
+
+ static final File STORE = Files.createTempDir();
+
+ private TestApplicationStore store = new TestApplicationStore();
+ private TestDelegate delegate = new TestDelegate();
+ private static final Object LOCK = new Object();
+
+ @Before
+ public void setUp() {
+ store.idStore = new TestIdStore();
+ store.setRootPath(STORE.getAbsolutePath());
+ store.setDelegate(delegate);
+ store.activate();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (STORE.exists()) {
+ Tools.removeDirectory(STORE);
+ }
+ store.deactivate();
+ }
+
+ private Application createTestApp() {
+ synchronized (LOCK) {
+ return store.create(ApplicationArchive.class.getResourceAsStream("app.zip"));
+ }
+ }
+
+ @Test
+ public void create() {
+ Application app = createTestApp();
+ assertEquals("incorrect name", "org.foo.app", app.id().name());
+ assertEquals("incorrect app count", 1, store.getApplications().size());
+ assertEquals("incorrect app", app, store.getApplication(app.id()));
+ assertEquals("incorrect app state", INSTALLED, store.getState(app.id()));
+ assertEquals("incorrect event type", APP_INSTALLED, delegate.event.type());
+ assertEquals("incorrect event app", app, delegate.event.subject());
+ }
+
+ @Test
+ public void remove() {
+ Application app = createTestApp();
+ store.remove(app.id());
+ assertEquals("incorrect app count", 0, store.getApplications().size());
+ assertEquals("incorrect event type", APP_UNINSTALLED, delegate.event.type());
+ assertEquals("incorrect event app", app, delegate.event.subject());
+ }
+
+ @Test
+ public void activate() {
+ Application app = createTestApp();
+ store.activate(app.id());
+ assertEquals("incorrect app count", 1, store.getApplications().size());
+ assertEquals("incorrect app state", ACTIVE, store.getState(app.id()));
+ assertEquals("incorrect event type", APP_ACTIVATED, delegate.event.type());
+ assertEquals("incorrect event app", app, delegate.event.subject());
+ }
+
+ @Test
+ public void deactivate() {
+ Application app = createTestApp();
+ store.deactivate(app.id());
+ assertEquals("incorrect app count", 1, store.getApplications().size());
+ assertEquals("incorrect app state", INSTALLED, store.getState(app.id()));
+ assertEquals("incorrect event type", APP_DEACTIVATED, delegate.event.type());
+ assertEquals("incorrect event app", app, delegate.event.subject());
+ }
+
+ @Test
+ public void permissions() {
+ Application app = createTestApp();
+ ImmutableSet<Permission> permissions =
+ ImmutableSet.of(new Permission(AppPermission.class.getName(), "FLOWRULE_WRITE"));
+ store.setPermissions(app.id(), permissions);
+ assertEquals("incorrect app perms", 1, store.getPermissions(app.id()).size());
+ assertEquals("incorrect app state", INSTALLED, store.getState(app.id()));
+ assertEquals("incorrect event type", APP_PERMISSIONS_CHANGED, delegate.event.type());
+ assertEquals("incorrect event app", app, delegate.event.subject());
+ }
+
+ private class TestIdStore extends ApplicationIdStoreAdapter {
+ @Override
+ public ApplicationId registerApplication(String name) {
+ return new DefaultApplicationId(1, name);
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ return new DefaultApplicationId(1, name);
+ }
+ }
+
+ private class TestDelegate implements ApplicationStoreDelegate {
+ private ApplicationEvent event;
+
+ @Override
+ public void notify(ApplicationEvent event) {
+ this.event = event;
+ }
+ }
+
+ private class TestApplicationStore extends SimpleApplicationStore {
+ @Override
+ public void setRootPath(String root) {
+ super.setRootPath(root);
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java
new file mode 100644
index 00000000..5eea3cc8
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java
@@ -0,0 +1,139 @@
+/*
+ * 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.store.trivial;
+
+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.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterStore;
+import org.onosproject.cluster.ClusterStoreDelegate;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.ListenerRegistry;
+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.store.AbstractStore;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of infrastructure devices using trivial in-memory
+ * structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleClusterStore
+ extends AbstractStore<ClusterEvent, ClusterStoreDelegate>
+ implements ClusterStore, PartitionService {
+
+ public static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
+
+ private final Logger log = getLogger(getClass());
+
+ private ControllerNode instance;
+
+ private final DateTime creationTime = DateTime.now();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ private ListenerRegistry<PartitionEvent, PartitionEventListener> listenerRegistry;
+
+ @Activate
+ public void activate() {
+ instance = new DefaultControllerNode(new NodeId("local"), LOCALHOST);
+
+ listenerRegistry = new ListenerRegistry<>();
+ eventDispatcher.addSink(PartitionEvent.class, listenerRegistry);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(PartitionEvent.class);
+ log.info("Stopped");
+ }
+
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return instance;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return ImmutableSet.of(instance);
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ return instance.id().equals(nodeId) ? instance : null;
+ }
+
+ @Override
+ public ControllerNode.State getState(NodeId nodeId) {
+ return ControllerNode.State.ACTIVE;
+ }
+
+ @Override
+ public DateTime getLastUpdated(NodeId nodeId) {
+ return creationTime;
+ }
+
+ @Override
+ public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
+ return null;
+ }
+
+ @Override
+ public void removeNode(NodeId nodeId) {
+ }
+
+ @Override
+ public boolean isMine(Key intentKey) {
+ return true;
+ }
+
+ @Override
+ public NodeId getLeader(Key intentKey) {
+ return instance.id();
+ }
+
+ @Override
+ public void addListener(PartitionEventListener listener) {
+ listenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(PartitionEventListener listener) {
+ listenerRegistry.removeListener(listener);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java
new file mode 100644
index 00000000..1d8bcd62
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java
@@ -0,0 +1,62 @@
+/*
+ * 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.store.trivial;
+
+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.onosproject.cfg.ComponentConfigEvent;
+import org.onosproject.cfg.ComponentConfigStore;
+import org.onosproject.cfg.ComponentConfigStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import static org.onosproject.cfg.ComponentConfigEvent.Type.PROPERTY_SET;
+import static org.onosproject.cfg.ComponentConfigEvent.Type.PROPERTY_UNSET;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of component configuration properties.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleComponentConfigStore
+ extends AbstractStore<ComponentConfigEvent, ComponentConfigStoreDelegate>
+ implements ComponentConfigStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public void setProperty(String componentName, String name, String value) {
+ delegate.notify(new ComponentConfigEvent(PROPERTY_SET, componentName, name, value));
+ }
+
+ @Override
+ public void unsetProperty(String componentName, String name) {
+ delegate.notify(new ComponentConfigEvent(PROPERTY_UNSET, componentName, name, null));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java
new file mode 100644
index 00000000..fc90dfad
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java
@@ -0,0 +1,691 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+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.Service;
+import org.onosproject.net.AnnotationsUtil;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.Device.Type;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+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.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onlab.packet.ChassisId;
+import org.onlab.util.NewConcurrentHashMap;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.base.Verify.verify;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onosproject.net.DefaultAnnotations.union;
+import static org.onosproject.net.DefaultAnnotations.merge;
+
+/**
+ * Manages inventory of infrastructure devices using trivial in-memory
+ * structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleDeviceStore
+ extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
+ implements DeviceStore {
+
+ private final Logger log = getLogger(getClass());
+
+ public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
+
+ // Collection of Description given from various providers
+ private final ConcurrentMap<DeviceId, Map<ProviderId, DeviceDescriptions>>
+ deviceDescs = Maps.newConcurrentMap();
+
+ // Cache of Device and Ports generated by compositing descriptions from providers
+ private final ConcurrentMap<DeviceId, Device> devices = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>>
+ devicePorts = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, PortStatistics>>
+ devicePortStats = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, PortStatistics>>
+ devicePortDeltaStats = Maps.newConcurrentMap();
+
+ // Available (=UP) devices
+ private final Set<DeviceId> availableDevices = Sets.newConcurrentHashSet();
+
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ deviceDescs.clear();
+ devices.clear();
+ devicePorts.clear();
+ availableDevices.clear();
+ log.info("Stopped");
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return devices.size();
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return Collections.unmodifiableCollection(devices.values());
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return FluentIterable.from(getDevices())
+ .filter(new Predicate<Device>() {
+
+ @Override
+ public boolean apply(Device input) {
+ return isAvailable(input.id());
+ }
+ });
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return devices.get(deviceId);
+ }
+
+ @Override
+ public DeviceEvent createOrUpdateDevice(ProviderId providerId,
+ DeviceId deviceId,
+ DeviceDescription deviceDescription) {
+ Map<ProviderId, DeviceDescriptions> providerDescs
+ = getOrCreateDeviceDescriptions(deviceId);
+
+ synchronized (providerDescs) {
+ // locking per device
+ DeviceDescriptions descs
+ = getOrCreateProviderDeviceDescriptions(providerDescs,
+ providerId,
+ deviceDescription);
+
+ Device oldDevice = devices.get(deviceId);
+ // update description
+ descs.putDeviceDesc(deviceDescription);
+ Device newDevice = composeDevice(deviceId, providerDescs);
+
+ if (oldDevice == null) {
+ // ADD
+ return createDevice(providerId, newDevice);
+ } else {
+ // UPDATE or ignore (no change or stale)
+ return updateDevice(providerId, oldDevice, newDevice);
+ }
+ }
+ }
+
+ // Creates the device and returns the appropriate event if necessary.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent createDevice(ProviderId providerId, Device newDevice) {
+ // update composed device cache
+ Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice);
+ verify(oldDevice == null,
+ "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
+ providerId, oldDevice, newDevice);
+
+ if (!providerId.isAncillary()) {
+ availableDevices.add(newDevice.id());
+ }
+
+ return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null);
+ }
+
+ // Updates the device and returns the appropriate event if necessary.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent updateDevice(ProviderId providerId, Device oldDevice, Device newDevice) {
+ // We allow only certain attributes to trigger update
+ boolean propertiesChanged =
+ !Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
+ !Objects.equals(oldDevice.swVersion(), newDevice.swVersion());
+ boolean annotationsChanged =
+ !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations());
+
+ // Primary providers can respond to all changes, but ancillary ones
+ // should respond only to annotation changes.
+ if ((providerId.isAncillary() && annotationsChanged) ||
+ (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) {
+
+ boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
+ if (!replaced) {
+ // FIXME: Is the enclosing if required here?
+ verify(replaced,
+ "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
+ providerId, oldDevice, devices.get(newDevice.id())
+ , newDevice);
+ }
+ if (!providerId.isAncillary()) {
+ availableDevices.add(newDevice.id());
+ }
+ return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null);
+ }
+
+ // Otherwise merely attempt to change availability if primary provider
+ if (!providerId.isAncillary()) {
+ boolean added = availableDevices.add(newDevice.id());
+ return !added ? null :
+ new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null);
+ }
+ return null;
+ }
+
+ @Override
+ public DeviceEvent markOffline(DeviceId deviceId) {
+ Map<ProviderId, DeviceDescriptions> providerDescs
+ = getOrCreateDeviceDescriptions(deviceId);
+
+ // locking device
+ synchronized (providerDescs) {
+ Device device = devices.get(deviceId);
+ if (device == null) {
+ return null;
+ }
+ boolean removed = availableDevices.remove(deviceId);
+ if (removed) {
+ // TODO: broadcast ... DOWN only?
+ return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public List<DeviceEvent> updatePorts(ProviderId providerId,
+ DeviceId deviceId,
+ List<PortDescription> portDescriptions) {
+ Device device = devices.get(deviceId);
+ if (device == null) {
+ log.debug("Device {} doesn't exist or hasn't been initialized yet", deviceId);
+ return Collections.emptyList();
+ }
+
+ Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+ checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+ List<DeviceEvent> events = new ArrayList<>();
+ synchronized (descsMap) {
+ DeviceDescriptions descs = descsMap.get(providerId);
+ // every provider must provide DeviceDescription.
+ checkArgument(descs != null,
+ "Device description for Device ID %s from Provider %s was not found",
+ deviceId, providerId);
+
+ Map<PortNumber, Port> ports = getPortMap(deviceId);
+
+ // Add new ports
+ Set<PortNumber> processed = new HashSet<>();
+ for (PortDescription portDescription : portDescriptions) {
+ final PortNumber number = portDescription.portNumber();
+ processed.add(portDescription.portNumber());
+
+ final Port oldPort = ports.get(number);
+ final Port newPort;
+
+// event suppression hook?
+
+ // update description
+ descs.putPortDesc(portDescription);
+ newPort = composePort(device, number, descsMap);
+
+ events.add(oldPort == null ?
+ createPort(device, newPort, ports) :
+ updatePort(device, oldPort, newPort, ports));
+ }
+
+ events.addAll(pruneOldPorts(device, ports, processed));
+ }
+ return FluentIterable.from(events).filter(notNull()).toList();
+ }
+
+ // Creates a new port based on the port description adds it to the map and
+ // Returns corresponding event.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent createPort(Device device, Port newPort,
+ Map<PortNumber, Port> ports) {
+ ports.put(newPort.number(), newPort);
+ return new DeviceEvent(PORT_ADDED, device, newPort);
+ }
+
+ // Checks if the specified port requires update and if so, it replaces the
+ // existing entry in the map and returns corresponding event.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent updatePort(Device device, Port oldPort,
+ Port newPort,
+ Map<PortNumber, Port> ports) {
+ if (oldPort.isEnabled() != newPort.isEnabled() ||
+ oldPort.type() != newPort.type() ||
+ oldPort.portSpeed() != newPort.portSpeed() ||
+ !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
+ ports.put(oldPort.number(), newPort);
+ return new DeviceEvent(PORT_UPDATED, device, newPort);
+ }
+ return null;
+ }
+
+ // Prunes the specified list of ports based on which ports are in the
+ // processed list and returns list of corresponding events.
+ // Guarded by deviceDescs value (=Device lock)
+ private List<DeviceEvent> pruneOldPorts(Device device,
+ Map<PortNumber, Port> ports,
+ Set<PortNumber> processed) {
+ List<DeviceEvent> events = new ArrayList<>();
+ Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<PortNumber, Port> e = iterator.next();
+ PortNumber portNumber = e.getKey();
+ if (!processed.contains(portNumber)) {
+ events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue()));
+ iterator.remove();
+ }
+ }
+ return events;
+ }
+
+ // Gets the map of ports for the specified device; if one does not already
+ // exist, it creates and registers a new one.
+ private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
+ return createIfAbsentUnchecked(devicePorts, deviceId,
+ NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
+ }
+
+ private Map<ProviderId, DeviceDescriptions> getOrCreateDeviceDescriptions(
+ DeviceId deviceId) {
+ Map<ProviderId, DeviceDescriptions> r;
+ r = deviceDescs.get(deviceId);
+ if (r != null) {
+ return r;
+ }
+ r = new HashMap<>();
+ final Map<ProviderId, DeviceDescriptions> concurrentlyAdded;
+ concurrentlyAdded = deviceDescs.putIfAbsent(deviceId, r);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ } else {
+ return r;
+ }
+ }
+
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceDescriptions getOrCreateProviderDeviceDescriptions(
+ Map<ProviderId, DeviceDescriptions> device,
+ ProviderId providerId, DeviceDescription deltaDesc) {
+ synchronized (device) {
+ DeviceDescriptions r = device.get(providerId);
+ if (r == null) {
+ r = new DeviceDescriptions(deltaDesc);
+ device.put(providerId, r);
+ }
+ return r;
+ }
+ }
+
+ @Override
+ public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
+ PortDescription portDescription) {
+ Device device = devices.get(deviceId);
+ checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+
+ Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+ checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+ synchronized (descsMap) {
+ DeviceDescriptions descs = descsMap.get(providerId);
+ // assuming all providers must give DeviceDescription first
+ checkArgument(descs != null,
+ "Device description for Device ID %s from Provider %s was not found",
+ deviceId, providerId);
+
+ ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
+ final PortNumber number = portDescription.portNumber();
+ final Port oldPort = ports.get(number);
+ final Port newPort;
+
+ // update description
+ descs.putPortDesc(portDescription);
+ newPort = composePort(device, number, descsMap);
+
+ if (oldPort == null) {
+ return createPort(device, newPort, ports);
+ } else {
+ return updatePort(device, oldPort, newPort, ports);
+ }
+ }
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ if (ports == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(ports.values());
+ }
+
+ @Override
+ public DeviceEvent updatePortStatistics(ProviderId providerId, DeviceId deviceId,
+ Collection<PortStatistics> newStatsCollection) {
+
+ ConcurrentMap<PortNumber, PortStatistics> prvStatsMap = devicePortStats.get(deviceId);
+ ConcurrentMap<PortNumber, PortStatistics> newStatsMap = Maps.newConcurrentMap();
+ ConcurrentMap<PortNumber, PortStatistics> deltaStatsMap = Maps.newConcurrentMap();
+
+ if (prvStatsMap != null) {
+ for (PortStatistics newStats : newStatsCollection) {
+ PortNumber port = PortNumber.portNumber(newStats.port());
+ PortStatistics prvStats = prvStatsMap.get(port);
+ DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
+ PortStatistics deltaStats = builder.build();
+ if (prvStats != null) {
+ deltaStats = calcDeltaStats(deviceId, prvStats, newStats);
+ }
+ deltaStatsMap.put(port, deltaStats);
+ newStatsMap.put(port, newStats);
+ }
+ } else {
+ for (PortStatistics newStats : newStatsCollection) {
+ PortNumber port = PortNumber.portNumber(newStats.port());
+ newStatsMap.put(port, newStats);
+ }
+ }
+ devicePortDeltaStats.put(deviceId, deltaStatsMap);
+ devicePortStats.put(deviceId, newStatsMap);
+ return new DeviceEvent(PORT_STATS_UPDATED, devices.get(deviceId), null);
+ }
+
+ public PortStatistics calcDeltaStats(DeviceId deviceId, PortStatistics prvStats, PortStatistics newStats) {
+ // calculate time difference
+ long deltaStatsSec, deltaStatsNano;
+ if (newStats.durationNano() < prvStats.durationNano()) {
+ deltaStatsNano = newStats.durationNano() - prvStats.durationNano() + TimeUnit.SECONDS.toNanos(1);
+ deltaStatsSec = newStats.durationSec() - prvStats.durationSec() - 1L;
+ } else {
+ deltaStatsNano = newStats.durationNano() - prvStats.durationNano();
+ deltaStatsSec = newStats.durationSec() - prvStats.durationSec();
+ }
+ DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
+ DefaultPortStatistics deltaStats = builder.setDeviceId(deviceId)
+ .setPort(newStats.port())
+ .setPacketsReceived(newStats.packetsReceived() - prvStats.packetsReceived())
+ .setPacketsSent(newStats.packetsSent() - prvStats.packetsSent())
+ .setBytesReceived(newStats.bytesReceived() - prvStats.bytesReceived())
+ .setBytesSent(newStats.bytesSent() - prvStats.bytesSent())
+ .setPacketsRxDropped(newStats.packetsRxDropped() - prvStats.packetsRxDropped())
+ .setPacketsTxDropped(newStats.packetsTxDropped() - prvStats.packetsTxDropped())
+ .setPacketsRxErrors(newStats.packetsRxErrors() - prvStats.packetsRxErrors())
+ .setPacketsTxErrors(newStats.packetsTxErrors() - prvStats.packetsTxErrors())
+ .setDurationSec(deltaStatsSec)
+ .setDurationNano(deltaStatsNano)
+ .build();
+ return deltaStats;
+ }
+
+ @Override
+ public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ return ports == null ? null : ports.get(portNumber);
+ }
+
+ @Override
+ public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortDeltaStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return availableDevices.contains(deviceId);
+ }
+
+ @Override
+ public DeviceEvent removeDevice(DeviceId deviceId) {
+ Map<ProviderId, DeviceDescriptions> descs = getOrCreateDeviceDescriptions(deviceId);
+ synchronized (descs) {
+ Device device = devices.remove(deviceId);
+ // should DEVICE_REMOVED carry removed ports?
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ if (ports != null) {
+ ports.clear();
+ }
+ availableDevices.remove(deviceId);
+ descs.clear();
+ return device == null ? null :
+ new DeviceEvent(DEVICE_REMOVED, device, null);
+ }
+ }
+
+ /**
+ * Returns a Device, merging description given from multiple Providers.
+ *
+ * @param deviceId device identifier
+ * @param providerDescs Collection of Descriptions from multiple providers
+ * @return Device instance
+ */
+ private Device composeDevice(DeviceId deviceId,
+ Map<ProviderId, DeviceDescriptions> providerDescs) {
+
+ checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
+
+ ProviderId primary = pickPrimaryPID(providerDescs);
+
+ DeviceDescriptions desc = providerDescs.get(primary);
+
+ final DeviceDescription base = desc.getDeviceDesc();
+ Type type = base.type();
+ String manufacturer = base.manufacturer();
+ String hwVersion = base.hwVersion();
+ String swVersion = base.swVersion();
+ String serialNumber = base.serialNumber();
+ ChassisId chassisId = base.chassisId();
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ annotations = merge(annotations, base.annotations());
+
+ for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
+ if (e.getKey().equals(primary)) {
+ continue;
+ }
+ // TODO: should keep track of Description timestamp
+ // and only merge conflicting keys when timestamp is newer
+ // Currently assuming there will never be a key conflict between
+ // providers
+
+ // annotation merging. not so efficient, should revisit later
+ annotations = merge(annotations, e.getValue().getDeviceDesc().annotations());
+ }
+
+ return new DefaultDevice(primary, deviceId, type, manufacturer,
+ hwVersion, swVersion, serialNumber,
+ chassisId, annotations);
+ }
+
+ /**
+ * Returns a Port, merging description given from multiple Providers.
+ *
+ * @param device device the port is on
+ * @param number port number
+ * @param descsMap Collection of Descriptions from multiple providers
+ * @return Port instance
+ */
+ private Port composePort(Device device, PortNumber number,
+ Map<ProviderId, DeviceDescriptions> descsMap) {
+
+ ProviderId primary = pickPrimaryPID(descsMap);
+ DeviceDescriptions primDescs = descsMap.get(primary);
+ // if no primary, assume not enabled
+ // TODO: revisit this default port enabled/disabled behavior
+ boolean isEnabled = false;
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+
+ final PortDescription portDesc = primDescs.getPortDesc(number);
+ if (portDesc != null) {
+ isEnabled = portDesc.isEnabled();
+ annotations = merge(annotations, portDesc.annotations());
+ }
+
+ for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) {
+ if (e.getKey().equals(primary)) {
+ continue;
+ }
+ // TODO: should keep track of Description timestamp
+ // and only merge conflicting keys when timestamp is newer
+ // Currently assuming there will never be a key conflict between
+ // providers
+
+ // annotation merging. not so efficient, should revisit later
+ final PortDescription otherPortDesc = e.getValue().getPortDesc(number);
+ if (otherPortDesc != null) {
+ annotations = merge(annotations, otherPortDesc.annotations());
+ }
+ }
+
+ return portDesc == null ?
+ new DefaultPort(device, number, false, annotations) :
+ new DefaultPort(device, number, isEnabled, portDesc.type(),
+ portDesc.portSpeed(), annotations);
+ }
+
+ /**
+ * @return primary ProviderID, or randomly chosen one if none exists
+ */
+ private ProviderId pickPrimaryPID(Map<ProviderId, DeviceDescriptions> descsMap) {
+ ProviderId fallBackPrimary = null;
+ for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) {
+ if (!e.getKey().isAncillary()) {
+ return e.getKey();
+ } else if (fallBackPrimary == null) {
+ // pick randomly as a fallback in case there is no primary
+ fallBackPrimary = e.getKey();
+ }
+ }
+ return fallBackPrimary;
+ }
+
+ /**
+ * Collection of Description of a Device and it's Ports given from a Provider.
+ */
+ private static class DeviceDescriptions {
+
+ private final AtomicReference<DeviceDescription> deviceDesc;
+ private final ConcurrentMap<PortNumber, PortDescription> portDescs;
+
+ public DeviceDescriptions(DeviceDescription desc) {
+ this.deviceDesc = new AtomicReference<>(checkNotNull(desc));
+ this.portDescs = new ConcurrentHashMap<>();
+ }
+
+ public DeviceDescription getDeviceDesc() {
+ return deviceDesc.get();
+ }
+
+ public PortDescription getPortDesc(PortNumber number) {
+ return portDescs.get(number);
+ }
+
+ /**
+ * Puts DeviceDescription, merging annotations as necessary.
+ *
+ * @param newDesc new DeviceDescription
+ * @return previous DeviceDescription
+ */
+ public synchronized DeviceDescription putDeviceDesc(DeviceDescription newDesc) {
+ DeviceDescription oldOne = deviceDesc.get();
+ DeviceDescription newOne = newDesc;
+ if (oldOne != null) {
+ SparseAnnotations merged = union(oldOne.annotations(),
+ newDesc.annotations());
+ newOne = new DefaultDeviceDescription(newOne, merged);
+ }
+ return deviceDesc.getAndSet(newOne);
+ }
+
+ /**
+ * Puts PortDescription, merging annotations as necessary.
+ *
+ * @param newDesc new PortDescription
+ * @return previous PortDescription
+ */
+ public synchronized PortDescription putPortDesc(PortDescription newDesc) {
+ PortDescription oldOne = portDescs.get(newDesc.portNumber());
+ PortDescription newOne = newDesc;
+ if (oldOne != null) {
+ SparseAnnotations merged = union(oldOne.annotations(),
+ newDesc.annotations());
+ newOne = new DefaultPortDescription(newOne, merged);
+ }
+ return portDescs.put(newOne.portNumber(), newOne);
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java
new file mode 100644
index 00000000..562e6f3c
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java
@@ -0,0 +1,530 @@
+/*
+ * 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.store.trivial;
+
+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.device.DeviceEvent.Type.*;
+import static org.onosproject.net.NetTestTools.assertAnnotationsEquals;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceStore;
+import org.onosproject.net.device.DeviceStoreDelegate;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.onlab.packet.ChassisId;
+
+/**
+ * Test of the simple DeviceStore implementation.
+ */
+public class SimpleDeviceStoreTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+ 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 SparseAnnotations A1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .build();
+ private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+ .remove("A1")
+ .set("B3", "b3")
+ .build();
+ private static final SparseAnnotations A2 = DefaultAnnotations.builder()
+ .set("A2", "a2")
+ .set("B2", "b2")
+ .build();
+ private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
+ .remove("A2")
+ .set("B4", "b4")
+ .build();
+
+ private SimpleDeviceStore simpleDeviceStore;
+ private DeviceStore deviceStore;
+
+
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ simpleDeviceStore = new SimpleDeviceStore();
+ simpleDeviceStore.activate();
+ deviceStore = simpleDeviceStore;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ simpleDeviceStore.deactivate();
+ }
+
+ private void putDevice(DeviceId deviceId, String swVersion,
+ SparseAnnotations... annotations) {
+ DeviceDescription description =
+ new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+ HW, swVersion, SN, CID, annotations);
+ deviceStore.createOrUpdateDevice(PID, deviceId, description);
+ }
+
+ private void putDeviceAncillary(DeviceId deviceId, String swVersion,
+ SparseAnnotations... annotations) {
+ DeviceDescription description =
+ new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+ HW, swVersion, SN, CID, annotations);
+ deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
+ }
+
+ private static void assertDevice(DeviceId id, String swVersion, Device device) {
+ assertNotNull(device);
+ assertEquals(id, device.id());
+ assertEquals(MFR, device.manufacturer());
+ assertEquals(HW, device.hwVersion());
+ assertEquals(swVersion, device.swVersion());
+ assertEquals(SN, device.serialNumber());
+ }
+
+ @Test
+ public final void testGetDeviceCount() {
+ assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
+
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW2);
+ putDevice(DID1, SW1);
+
+ assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
+ }
+
+ @Test
+ public final void testGetDevices() {
+ assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
+
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW2);
+ putDevice(DID1, SW1);
+
+ assertEquals("expect 2 uniq devices",
+ 2, Iterables.size(deviceStore.getDevices()));
+
+ Map<DeviceId, Device> devices = new HashMap<>();
+ for (Device device : deviceStore.getDevices()) {
+ devices.put(device.id(), device);
+ }
+
+ assertDevice(DID1, SW1, devices.get(DID1));
+ assertDevice(DID2, SW2, devices.get(DID2));
+
+ // add case for new node?
+ }
+
+ @Test
+ public final void testGetDevice() {
+
+ putDevice(DID1, SW1);
+
+ assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
+ assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
+ }
+
+ @Test
+ public final void testCreateOrUpdateDevice() {
+ DeviceDescription description =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID);
+ DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
+ assertEquals(DEVICE_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+
+ DeviceDescription description2 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW2, SN, CID);
+ DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+ assertEquals(DEVICE_UPDATED, event2.type());
+ assertDevice(DID1, SW2, event2.subject());
+
+ assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+ }
+
+ @Test
+ public final void testCreateOrUpdateDeviceAncillary() {
+ DeviceDescription description =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID, A2);
+ DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description);
+ assertEquals(DEVICE_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertEquals(PIDA, event.subject().providerId());
+ assertAnnotationsEquals(event.subject().annotations(), A2);
+ assertFalse("Ancillary will not bring device up", deviceStore.isAvailable(DID1));
+
+ DeviceDescription description2 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW2, SN, CID, A1);
+ DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+ assertEquals(DEVICE_UPDATED, event2.type());
+ assertDevice(DID1, SW2, event2.subject());
+ assertEquals(PID, event2.subject().providerId());
+ assertAnnotationsEquals(event2.subject().annotations(), A1, A2);
+ assertTrue(deviceStore.isAvailable(DID1));
+
+ assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+
+ // For now, Ancillary is ignored once primary appears
+ assertNull("No change expected", deviceStore.createOrUpdateDevice(PIDA, DID1, description));
+
+ // But, Ancillary annotations will be in effect
+ DeviceDescription description3 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID, A2_2);
+ DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
+ assertEquals(DEVICE_UPDATED, event3.type());
+ // basic information will be the one from Primary
+ assertDevice(DID1, SW2, event3.subject());
+ assertEquals(PID, event3.subject().providerId());
+ // but annotation from Ancillary will be merged
+ assertAnnotationsEquals(event3.subject().annotations(), A1, A2, A2_2);
+ assertTrue(deviceStore.isAvailable(DID1));
+ }
+
+
+ @Test
+ public final void testMarkOffline() {
+
+ putDevice(DID1, SW1);
+ assertTrue(deviceStore.isAvailable(DID1));
+
+ DeviceEvent event = deviceStore.markOffline(DID1);
+ assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertFalse(deviceStore.isAvailable(DID1));
+
+ DeviceEvent event2 = deviceStore.markOffline(DID1);
+ assertNull("No change, no event", event2);
+}
+
+ @Test
+ public final void testUpdatePorts() {
+ putDevice(DID1, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, true)
+ );
+
+ List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
+
+ Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+ for (DeviceEvent event : events) {
+ assertEquals(PORT_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertTrue("PortNumber is one of expected",
+ expectedPorts.remove(event.port().number()));
+ assertTrue("Port is enabled", event.port().isEnabled());
+ }
+ assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+ List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, false),
+ new DefaultPortDescription(P2, true),
+ new DefaultPortDescription(P3, true)
+ );
+
+ events = deviceStore.updatePorts(PID, DID1, pds2);
+ assertFalse("event should be triggered", events.isEmpty());
+ for (DeviceEvent event : events) {
+ PortNumber num = event.port().number();
+ if (P1.equals(num)) {
+ assertEquals(PORT_UPDATED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertFalse("Port is disabled", event.port().isEnabled());
+ } else if (P2.equals(num)) {
+ fail("P2 event not expected.");
+ } else if (P3.equals(num)) {
+ assertEquals(PORT_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertTrue("Port is enabled", event.port().isEnabled());
+ } else {
+ fail("Unknown port number encountered: " + num);
+ }
+ }
+
+ List<PortDescription> pds3 = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, false),
+ new DefaultPortDescription(P2, true)
+ );
+ events = deviceStore.updatePorts(PID, DID1, pds3);
+ assertFalse("event should be triggered", events.isEmpty());
+ for (DeviceEvent event : events) {
+ PortNumber num = event.port().number();
+ if (P1.equals(num)) {
+ fail("P1 event not expected.");
+ } else if (P2.equals(num)) {
+ fail("P2 event not expected.");
+ } else if (P3.equals(num)) {
+ assertEquals(PORT_REMOVED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertTrue("Port was enabled", event.port().isEnabled());
+ } else {
+ fail("Unknown port number encountered: " + num);
+ }
+ }
+
+ }
+
+ @Test
+ public final void testUpdatePortStatus() {
+ putDevice(DID1, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
+ new DefaultPortDescription(P1, false));
+ assertEquals(PORT_UPDATED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertEquals(P1, event.port().number());
+ assertFalse("Port is disabled", event.port().isEnabled());
+
+ }
+
+ @Test
+ public final void testUpdatePortStatusAncillary() {
+ putDeviceAncillary(DID1, SW1);
+ putDevice(DID1, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true, A1)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
+ new DefaultPortDescription(P1, false, A1_2));
+ assertEquals(PORT_UPDATED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertEquals(P1, event.port().number());
+ assertAnnotationsEquals(event.port().annotations(), A1, A1_2);
+ assertFalse("Port is disabled", event.port().isEnabled());
+
+ DeviceEvent event2 = deviceStore.updatePortStatus(PIDA, DID1,
+ new DefaultPortDescription(P1, true));
+ assertNull("Ancillary is ignored if primary exists", event2);
+
+ // but, Ancillary annotation update will be notified
+ DeviceEvent event3 = deviceStore.updatePortStatus(PIDA, DID1,
+ new DefaultPortDescription(P1, true, A2));
+ assertEquals(PORT_UPDATED, event3.type());
+ assertDevice(DID1, SW1, event3.subject());
+ assertEquals(P1, event3.port().number());
+ assertAnnotationsEquals(event3.port().annotations(), A1, A1_2, A2);
+ assertFalse("Port is disabled", event3.port().isEnabled());
+
+ // port only reported from Ancillary will be notified as down
+ DeviceEvent event4 = deviceStore.updatePortStatus(PIDA, DID1,
+ new DefaultPortDescription(P2, true));
+ assertEquals(PORT_ADDED, event4.type());
+ assertDevice(DID1, SW1, event4.subject());
+ assertEquals(P2, event4.port().number());
+ assertAnnotationsEquals(event4.port().annotations());
+ assertFalse("Port is disabled if not given from primary provider",
+ event4.port().isEnabled());
+ }
+
+ @Test
+ public final void testGetPorts() {
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+ List<Port> ports = deviceStore.getPorts(DID1);
+ for (Port port : ports) {
+ assertTrue("Port is enabled", port.isEnabled());
+ assertTrue("PortNumber is one of expected",
+ expectedPorts.remove(port.number()));
+ }
+ assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+ assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
+ }
+
+ @Test
+ public final void testGetPort() {
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, false)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Port port1 = deviceStore.getPort(DID1, P1);
+ assertEquals(P1, port1.number());
+ assertTrue("Port is enabled", port1.isEnabled());
+
+ Port port2 = deviceStore.getPort(DID1, P2);
+ assertEquals(P2, port2.number());
+ assertFalse("Port is disabled", port2.isEnabled());
+
+ Port port3 = deviceStore.getPort(DID1, P3);
+ assertNull("P3 not expected", port3);
+ }
+
+ @Test
+ public final void testRemoveDevice() {
+ putDevice(DID1, SW1, A1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true, A2)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+ putDevice(DID2, SW1);
+
+ assertEquals(2, deviceStore.getDeviceCount());
+ assertEquals(1, deviceStore.getPorts(DID1).size());
+ assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations(), A1);
+ assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations(), A2);
+
+ DeviceEvent event = deviceStore.removeDevice(DID1);
+ assertEquals(DEVICE_REMOVED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+
+ assertEquals(1, deviceStore.getDeviceCount());
+ assertEquals(0, deviceStore.getPorts(DID1).size());
+
+ // putBack Device, Port w/o annotation
+ putDevice(DID1, SW1);
+ List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds2);
+
+ // annotations should not survive
+ assertEquals(2, deviceStore.getDeviceCount());
+ assertEquals(1, deviceStore.getPorts(DID1).size());
+ assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations());
+ assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations());
+ }
+
+ // If Delegates should be called only on remote events,
+ // then Simple* should never call them, thus not test required.
+ // TODO add test for Port events when we have them
+ @Ignore("Ignore until Delegate spec. is clear.")
+ @Test
+ public final void testEvents() throws InterruptedException {
+ final CountDownLatch addLatch = new CountDownLatch(1);
+ DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
+ @Override
+ public void notify(DeviceEvent event) {
+ assertEquals(DEVICE_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ addLatch.countDown();
+ }
+ };
+ final CountDownLatch updateLatch = new CountDownLatch(1);
+ DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
+ @Override
+ public void notify(DeviceEvent event) {
+ assertEquals(DEVICE_UPDATED, event.type());
+ assertDevice(DID1, SW2, event.subject());
+ updateLatch.countDown();
+ }
+ };
+ final CountDownLatch removeLatch = new CountDownLatch(1);
+ DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
+ @Override
+ public void notify(DeviceEvent event) {
+ assertEquals(DEVICE_REMOVED, event.type());
+ assertDevice(DID1, SW2, event.subject());
+ removeLatch.countDown();
+ }
+ };
+
+ DeviceDescription description =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID);
+ deviceStore.setDelegate(checkAdd);
+ deviceStore.createOrUpdateDevice(PID, DID1, description);
+ assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+
+
+ DeviceDescription description2 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW2, SN, CID);
+ deviceStore.unsetDelegate(checkAdd);
+ deviceStore.setDelegate(checkUpdate);
+ deviceStore.createOrUpdateDevice(PID, DID1, description2);
+ assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
+
+ deviceStore.unsetDelegate(checkUpdate);
+ deviceStore.setDelegate(checkRemove);
+ deviceStore.removeDevice(DID1);
+ assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java
new file mode 100644
index 00000000..3b8f1d35
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java
@@ -0,0 +1,327 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.base.Function;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.SettableFuture;
+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.NewConcurrentHashMap;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowEntry.FlowEntryState;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleBatchEntry;
+import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+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.FlowRuleEvent.Type;
+import org.onosproject.net.flow.FlowRuleStore;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of flow rules using trivial in-memory implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleFlowRuleStore
+ extends AbstractStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+ implements FlowRuleStore {
+
+ private final Logger log = getLogger(getClass());
+
+
+ // inner Map is Device flow table
+ // inner Map value (FlowId synonym list) must be synchronized before modifying
+ private final ConcurrentMap<DeviceId, ConcurrentMap<FlowId, List<StoredFlowEntry>>>
+ flowEntries = new ConcurrentHashMap<>();
+
+ private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+ // TODO: make this configurable
+ private int pendingFutureTimeoutMinutes = 5;
+
+ private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
+ CacheBuilder.newBuilder()
+ .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+ .removalListener(new TimeoutFuture())
+ .build();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ flowEntries.clear();
+ log.info("Stopped");
+ }
+
+
+ @Override
+ public int getFlowRuleCount() {
+ int sum = 0;
+ for (ConcurrentMap<FlowId, List<StoredFlowEntry>> ft : flowEntries.values()) {
+ for (List<StoredFlowEntry> fes : ft.values()) {
+ sum += fes.size();
+ }
+ }
+ return sum;
+ }
+
+ private static NewConcurrentHashMap<FlowId, List<StoredFlowEntry>> lazyEmptyFlowTable() {
+ return NewConcurrentHashMap.<FlowId, List<StoredFlowEntry>>ifNeeded();
+ }
+
+ /**
+ * Returns the flow table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing Flow Table of given device.
+ */
+ private ConcurrentMap<FlowId, List<StoredFlowEntry>> getFlowTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(flowEntries,
+ deviceId, lazyEmptyFlowTable());
+ }
+
+ private List<StoredFlowEntry> getFlowEntries(DeviceId deviceId, FlowId flowId) {
+ final ConcurrentMap<FlowId, List<StoredFlowEntry>> flowTable = getFlowTable(deviceId);
+ List<StoredFlowEntry> r = flowTable.get(flowId);
+ if (r == null) {
+ final List<StoredFlowEntry> concurrentlyAdded;
+ r = new CopyOnWriteArrayList<>();
+ concurrentlyAdded = flowTable.putIfAbsent(flowId, r);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ }
+ }
+ return r;
+ }
+
+ private FlowEntry getFlowEntryInternal(DeviceId deviceId, FlowRule rule) {
+ List<StoredFlowEntry> fes = getFlowEntries(deviceId, rule.id());
+ for (StoredFlowEntry fe : fes) {
+ if (fe.equals(rule)) {
+ return fe;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public FlowEntry getFlowEntry(FlowRule rule) {
+ return getFlowEntryInternal(rule.deviceId(), rule);
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(getFlowTable(deviceId).values())
+ .transformAndConcat(
+ new Function<List<StoredFlowEntry>, Iterable<? extends FlowEntry>>() {
+
+ @Override
+ public Iterable<? extends FlowEntry> apply(
+ List<StoredFlowEntry> input) {
+ return Collections.unmodifiableList(input);
+ }
+ });
+ }
+
+ @Override
+ public void storeFlowRule(FlowRule rule) {
+ storeFlowRuleInternal(rule);
+ }
+
+ private void storeFlowRuleInternal(FlowRule rule) {
+ StoredFlowEntry f = new DefaultFlowEntry(rule);
+ final DeviceId did = f.deviceId();
+ final FlowId fid = f.id();
+ List<StoredFlowEntry> existing = getFlowEntries(did, fid);
+ synchronized (existing) {
+ for (StoredFlowEntry fe : existing) {
+ if (fe.equals(rule)) {
+ // was already there? ignore
+ return;
+ }
+ }
+ // new flow rule added
+ existing.add(f);
+ }
+ }
+
+ @Override
+ public void deleteFlowRule(FlowRule rule) {
+
+ List<StoredFlowEntry> entries = getFlowEntries(rule.deviceId(), rule.id());
+
+ synchronized (entries) {
+ for (StoredFlowEntry entry : entries) {
+ if (entry.equals(rule)) {
+ synchronized (entry) {
+ entry.setState(FlowEntryState.PENDING_REMOVE);
+ }
+ }
+ }
+ }
+
+
+ //log.warn("Cannot find rule {}", rule);
+ }
+
+ @Override
+ public FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
+ // check if this new rule is an update to an existing entry
+ List<StoredFlowEntry> entries = getFlowEntries(rule.deviceId(), rule.id());
+ synchronized (entries) {
+ for (StoredFlowEntry stored : entries) {
+ if (stored.equals(rule)) {
+ synchronized (stored) {
+ stored.setBytes(rule.bytes());
+ stored.setLife(rule.life());
+ stored.setPackets(rule.packets());
+ if (stored.state() == FlowEntryState.PENDING_ADD) {
+ stored.setState(FlowEntryState.ADDED);
+ // TODO: Do we need to change `rule` state?
+ return new FlowRuleEvent(Type.RULE_ADDED, rule);
+ }
+ return new FlowRuleEvent(Type.RULE_UPDATED, rule);
+ }
+ }
+ }
+ }
+
+ // should not reach here
+ // storeFlowRule was expected to be called
+ log.error("FlowRule was not found in store {} to update", rule);
+
+ //flowEntries.put(did, rule);
+ return null;
+ }
+
+ @Override
+ public FlowRuleEvent removeFlowRule(FlowEntry rule) {
+ // This is where one could mark a rule as removed and still keep it in the store.
+ final DeviceId did = rule.deviceId();
+
+ List<StoredFlowEntry> entries = getFlowEntries(did, rule.id());
+ synchronized (entries) {
+ if (entries.remove(rule)) {
+ return new FlowRuleEvent(RULE_REMOVED, rule);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void storeBatch(
+ FlowRuleBatchOperation operation) {
+ List<FlowRuleBatchEntry> toAdd = new ArrayList<>();
+ List<FlowRuleBatchEntry> toRemove = new ArrayList<>();
+
+ for (FlowRuleBatchEntry entry : operation.getOperations()) {
+ final FlowRule flowRule = entry.target();
+ if (entry.operator().equals(FlowRuleOperation.ADD)) {
+ if (!getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+ storeFlowRule(flowRule);
+ toAdd.add(entry);
+ }
+ } else if (entry.operator().equals(FlowRuleOperation.REMOVE)) {
+ if (getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+ deleteFlowRule(flowRule);
+ toRemove.add(entry);
+ }
+ } else {
+ throw new UnsupportedOperationException("Unsupported operation type");
+ }
+ }
+
+ if (toAdd.isEmpty() && toRemove.isEmpty()) {
+ notifyDelegate(FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(),
+ operation.deviceId())));
+ return;
+ }
+
+ SettableFuture<CompletedBatchOperation> r = SettableFuture.create();
+ final int batchId = localBatchIdGen.incrementAndGet();
+
+ pendingFutures.put(batchId, r);
+
+ toAdd.addAll(toRemove);
+ notifyDelegate(FlowRuleBatchEvent.requested(
+ new FlowRuleBatchRequest(batchId, Sets.newHashSet(toAdd)), operation.deviceId()));
+
+ }
+
+ @Override
+ public void batchOperationComplete(FlowRuleBatchEvent event) {
+ final Long batchId = event.subject().batchId();
+ SettableFuture<CompletedBatchOperation> future
+ = pendingFutures.getIfPresent(batchId);
+ if (future != null) {
+ future.set(event.result());
+ pendingFutures.invalidate(batchId);
+ }
+ notifyDelegate(event);
+ }
+
+ private static final class TimeoutFuture
+ implements RemovalListener<Integer, SettableFuture<CompletedBatchOperation>> {
+ @Override
+ public void onRemoval(RemovalNotification<Integer, SettableFuture<CompletedBatchOperation>> notification) {
+ // wrapping in ExecutionException to support Future.get
+ if (notification.wasEvicted()) {
+ notification.getValue()
+ .setException(new ExecutionException("Timed out",
+ new TimeoutException()));
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java
new file mode 100644
index 00000000..71de3e13
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java
@@ -0,0 +1,717 @@
+/*
+ * 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.store.trivial;
+
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.NewConcurrentHashMap;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.Group.GroupState;
+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.GroupEvent.Type;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+
+/**
+ * Manages inventory of group entries using trivial in-memory implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleGroupStore
+ extends AbstractStore<GroupEvent, GroupStoreDelegate>
+ implements GroupStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final int dummyId = 0xffffffff;
+ private final GroupId dummyGroupId = new DefaultGroupId(dummyId);
+
+ // inner Map is per device group table
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
+ groupEntriesByKey = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>
+ groupEntriesById = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
+ pendingGroupEntriesByKey = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, Group>>
+ extraneousGroupEntriesById = new ConcurrentHashMap<>();
+
+ private final HashMap<DeviceId, Boolean> deviceAuditStatus =
+ new HashMap<DeviceId, Boolean>();
+
+ private final AtomicInteger groupIdGen = new AtomicInteger();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ groupEntriesByKey.clear();
+ groupEntriesById.clear();
+ log.info("Stopped");
+ }
+
+ private static NewConcurrentHashMap<GroupKey, StoredGroupEntry>
+ lazyEmptyGroupKeyTable() {
+ return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupId, StoredGroupEntry>
+ lazyEmptyGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupKey, StoredGroupEntry>
+ lazyEmptyPendingGroupKeyTable() {
+ return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupId, Group>
+ lazyEmptyExtraneousGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, Group>ifNeeded();
+ }
+
+ /**
+ * Returns the group key table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupKey, StoredGroupEntry> getGroupKeyTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(groupEntriesByKey,
+ deviceId, lazyEmptyGroupKeyTable());
+ }
+
+ /**
+ * Returns the group id table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupId, StoredGroupEntry> getGroupIdTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(groupEntriesById,
+ deviceId, lazyEmptyGroupIdTable());
+ }
+
+ /**
+ * Returns the pending group key table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupKey, StoredGroupEntry>
+ getPendingGroupKeyTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(pendingGroupEntriesByKey,
+ deviceId, lazyEmptyPendingGroupKeyTable());
+ }
+
+ /**
+ * Returns the extraneous group id table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupId, Group>
+ getExtraneousGroupIdTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(extraneousGroupEntriesById,
+ deviceId,
+ lazyEmptyExtraneousGroupIdTable());
+ }
+
+ /**
+ * Returns the number of groups for the specified device in the store.
+ *
+ * @return number of groups for the specified device
+ */
+ @Override
+ public int getGroupCount(DeviceId deviceId) {
+ return (groupEntriesByKey.get(deviceId) != null) ?
+ groupEntriesByKey.get(deviceId).size() : 0;
+ }
+
+ /**
+ * Returns the groups associated with a device.
+ *
+ * @param deviceId the device ID
+ *
+ * @return the group entries
+ */
+ @Override
+ public Iterable<Group> getGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(getGroupKeyTable(deviceId).values())
+ .transform(
+ new Function<StoredGroupEntry, Group>() {
+
+ @Override
+ public Group apply(
+ StoredGroupEntry input) {
+ return input;
+ }
+ });
+ }
+
+ /**
+ * Returns the stored group entry.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ *
+ * @return a group associated with the key
+ */
+ @Override
+ public Group getGroup(DeviceId deviceId, GroupKey appCookie) {
+ return (groupEntriesByKey.get(deviceId) != null) ?
+ groupEntriesByKey.get(deviceId).get(appCookie) :
+ null;
+ }
+
+ @Override
+ public Group getGroup(DeviceId deviceId, GroupId groupId) {
+ return (groupEntriesById.get(deviceId) != null) ?
+ groupEntriesById.get(deviceId).get(groupId) :
+ null;
+ }
+
+ private int getFreeGroupIdValue(DeviceId deviceId) {
+ int freeId = groupIdGen.incrementAndGet();
+
+ while (true) {
+ Group existing = (
+ groupEntriesById.get(deviceId) != null) ?
+ groupEntriesById.get(deviceId).get(new DefaultGroupId(freeId)) :
+ null;
+ if (existing == null) {
+ existing = (
+ extraneousGroupEntriesById.get(deviceId) != null) ?
+ extraneousGroupEntriesById.get(deviceId).
+ get(new DefaultGroupId(freeId)) :
+ null;
+ }
+ if (existing != null) {
+ freeId = groupIdGen.incrementAndGet();
+ } else {
+ break;
+ }
+ }
+ return freeId;
+ }
+
+ /**
+ * Stores a new group entry using the information from group description.
+ *
+ * @param groupDesc group description to be used to create group entry
+ */
+ @Override
+ public void storeGroupDescription(GroupDescription groupDesc) {
+ // Check if a group is existing with the same key
+ if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+ return;
+ }
+
+ if (deviceAuditStatus.get(groupDesc.deviceId()) == null) {
+ // Device group audit has not completed yet
+ // Add this group description to pending group key table
+ // Create a group entry object with Dummy Group ID
+ StoredGroupEntry group = new DefaultGroup(dummyGroupId, groupDesc);
+ group.setState(GroupState.WAITING_AUDIT_COMPLETE);
+ ConcurrentMap<GroupKey, StoredGroupEntry> pendingKeyTable =
+ getPendingGroupKeyTable(groupDesc.deviceId());
+ pendingKeyTable.put(groupDesc.appCookie(), group);
+ return;
+ }
+
+ storeGroupDescriptionInternal(groupDesc);
+ }
+
+ private void storeGroupDescriptionInternal(GroupDescription groupDesc) {
+ // Check if a group is existing with the same key
+ if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+ return;
+ }
+
+ GroupId id = null;
+ if (groupDesc.givenGroupId() == null) {
+ // Get a new group identifier
+ id = new DefaultGroupId(getFreeGroupIdValue(groupDesc.deviceId()));
+ } else {
+ id = new DefaultGroupId(groupDesc.givenGroupId());
+ }
+ // Create a group entry object
+ StoredGroupEntry group = new DefaultGroup(id, groupDesc);
+ // Insert the newly created group entry into concurrent key and id maps
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(groupDesc.deviceId());
+ keyTable.put(groupDesc.appCookie(), group);
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(groupDesc.deviceId());
+ idTable.put(id, group);
+ notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+ group));
+ }
+
+ /**
+ * Updates the existing group entry with the information
+ * from group description.
+ *
+ * @param deviceId the device ID
+ * @param oldAppCookie the current group key
+ * @param type update type
+ * @param newBuckets group buckets for updates
+ * @param newAppCookie optional new group key
+ */
+ @Override
+ public void updateGroupDescription(DeviceId deviceId,
+ GroupKey oldAppCookie,
+ UpdateType type,
+ GroupBuckets newBuckets,
+ GroupKey newAppCookie) {
+ // Check if a group is existing with the provided key
+ Group oldGroup = getGroup(deviceId, oldAppCookie);
+ if (oldGroup == null) {
+ return;
+ }
+
+ List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup,
+ type,
+ newBuckets);
+ if (newBucketList != null) {
+ // Create a new group object from the old group
+ GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+ GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie;
+ GroupDescription updatedGroupDesc = new DefaultGroupDescription(
+ oldGroup.deviceId(),
+ oldGroup.type(),
+ updatedBuckets,
+ newCookie,
+ oldGroup.givenGroupId(),
+ oldGroup.appId());
+ StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(),
+ updatedGroupDesc);
+ newGroup.setState(GroupState.PENDING_UPDATE);
+ newGroup.setLife(oldGroup.life());
+ newGroup.setPackets(oldGroup.packets());
+ newGroup.setBytes(oldGroup.bytes());
+ // Remove the old entry from maps and add new entry using new key
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(oldGroup.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(oldGroup.deviceId());
+ keyTable.remove(oldGroup.appCookie());
+ idTable.remove(oldGroup.id());
+ keyTable.put(newGroup.appCookie(), newGroup);
+ idTable.put(newGroup.id(), newGroup);
+ notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_REQUESTED, newGroup));
+ }
+ }
+
+ private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
+ UpdateType type,
+ GroupBuckets buckets) {
+ GroupBuckets oldBuckets = oldGroup.buckets();
+ List<GroupBucket> newBucketList = new ArrayList<GroupBucket>(
+ oldBuckets.buckets());
+ boolean groupDescUpdated = false;
+
+ if (type == UpdateType.ADD) {
+ // Check if the any of the new buckets are part of
+ // the old bucket list
+ for (GroupBucket addBucket:buckets.buckets()) {
+ if (!newBucketList.contains(addBucket)) {
+ newBucketList.add(addBucket);
+ groupDescUpdated = true;
+ }
+ }
+ } else if (type == UpdateType.REMOVE) {
+ // Check if the to be removed buckets are part of the
+ // old bucket list
+ for (GroupBucket removeBucket:buckets.buckets()) {
+ if (newBucketList.contains(removeBucket)) {
+ newBucketList.remove(removeBucket);
+ groupDescUpdated = true;
+ }
+ }
+ }
+
+ if (groupDescUpdated) {
+ return newBucketList;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Triggers deleting the existing group entry.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ */
+ @Override
+ public void deleteGroupDescription(DeviceId deviceId,
+ GroupKey appCookie) {
+ // Check if a group is existing with the provided key
+ StoredGroupEntry existing = (groupEntriesByKey.get(deviceId) != null) ?
+ groupEntriesByKey.get(deviceId).get(appCookie) :
+ null;
+ if (existing == null) {
+ return;
+ }
+
+ synchronized (existing) {
+ existing.setState(GroupState.PENDING_DELETE);
+ }
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, existing));
+ }
+
+ /**
+ * Stores a new group entry, or updates an existing entry.
+ *
+ * @param group group entry
+ */
+ @Override
+ public void addOrUpdateGroupEntry(Group group) {
+ // check if this new entry is an update to an existing entry
+ StoredGroupEntry existing = (groupEntriesById.get(
+ group.deviceId()) != null) ?
+ groupEntriesById.get(group.deviceId()).get(group.id()) :
+ null;
+ GroupEvent event = null;
+
+ if (existing != null) {
+ synchronized (existing) {
+ for (GroupBucket bucket:group.buckets().buckets()) {
+ Optional<GroupBucket> matchingBucket =
+ existing.buckets().buckets()
+ .stream()
+ .filter((existingBucket)->(existingBucket.equals(bucket)))
+ .findFirst();
+ if (matchingBucket.isPresent()) {
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setPackets(bucket.packets());
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setBytes(bucket.bytes());
+ } else {
+ log.warn("addOrUpdateGroupEntry: No matching "
+ + "buckets to update stats");
+ }
+ }
+ existing.setLife(group.life());
+ existing.setPackets(group.packets());
+ existing.setBytes(group.bytes());
+ if (existing.state() == GroupState.PENDING_ADD) {
+ existing.setState(GroupState.ADDED);
+ event = new GroupEvent(Type.GROUP_ADDED, existing);
+ } else {
+ if (existing.state() == GroupState.PENDING_UPDATE) {
+ existing.setState(GroupState.ADDED);
+ }
+ event = new GroupEvent(Type.GROUP_UPDATED, existing);
+ }
+ }
+ }
+
+ if (event != null) {
+ notifyDelegate(event);
+ }
+ }
+
+ /**
+ * Removes the group entry from store.
+ *
+ * @param group group entry
+ */
+ @Override
+ public void removeGroupEntry(Group group) {
+ StoredGroupEntry existing = (groupEntriesById.get(
+ group.deviceId()) != null) ?
+ groupEntriesById.get(group.deviceId()).get(group.id()) :
+ null;
+
+ if (existing != null) {
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(existing.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(existing.deviceId());
+ idTable.remove(existing.id());
+ keyTable.remove(existing.appCookie());
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, existing));
+ }
+ }
+
+ @Override
+ public void deviceInitialAuditCompleted(DeviceId deviceId,
+ boolean completed) {
+ synchronized (deviceAuditStatus) {
+ if (completed) {
+ log.debug("deviceInitialAuditCompleted: AUDIT "
+ + "completed for device {}", deviceId);
+ deviceAuditStatus.put(deviceId, true);
+ // Execute all pending group requests
+ ConcurrentMap<GroupKey, StoredGroupEntry> pendingGroupRequests =
+ getPendingGroupKeyTable(deviceId);
+ for (Group group:pendingGroupRequests.values()) {
+ GroupDescription tmp = new DefaultGroupDescription(
+ group.deviceId(),
+ group.type(),
+ group.buckets(),
+ group.appCookie(),
+ group.givenGroupId(),
+ group.appId());
+ storeGroupDescriptionInternal(tmp);
+ }
+ getPendingGroupKeyTable(deviceId).clear();
+ } else {
+ if (deviceAuditStatus.get(deviceId)) {
+ log.debug("deviceInitialAuditCompleted: Clearing AUDIT "
+ + "status for device {}", deviceId);
+ deviceAuditStatus.put(deviceId, false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean deviceInitialAuditStatus(DeviceId deviceId) {
+ synchronized (deviceAuditStatus) {
+ return (deviceAuditStatus.get(deviceId) != null)
+ ? deviceAuditStatus.get(deviceId) : false;
+ }
+ }
+
+ @Override
+ public void groupOperationFailed(DeviceId deviceId, GroupOperation operation) {
+
+ StoredGroupEntry existing = (groupEntriesById.get(
+ deviceId) != null) ?
+ groupEntriesById.get(deviceId).get(operation.groupId()) :
+ null;
+
+ if (existing == null) {
+ log.warn("No group entry with ID {} found ", operation.groupId());
+ return;
+ }
+
+ switch (operation.opType()) {
+ case ADD:
+ notifyDelegate(new GroupEvent(Type.GROUP_ADD_FAILED, existing));
+ break;
+ case MODIFY:
+ notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_FAILED, existing));
+ break;
+ case DELETE:
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_FAILED, existing));
+ break;
+ default:
+ log.warn("Unknown group operation type {}", operation.opType());
+ }
+
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(existing.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(existing.deviceId());
+ idTable.remove(existing.id());
+ keyTable.remove(existing.appCookie());
+ }
+
+ @Override
+ public void addOrUpdateExtraneousGroupEntry(Group group) {
+ ConcurrentMap<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(group.deviceId());
+ extraneousIdTable.put(group.id(), group);
+ // Check the reference counter
+ if (group.referenceCount() == 0) {
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, group));
+ }
+ }
+
+ @Override
+ public void removeExtraneousGroupEntry(Group group) {
+ ConcurrentMap<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(group.deviceId());
+ extraneousIdTable.remove(group.id());
+ }
+
+ @Override
+ public Iterable<Group> getExtraneousGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(
+ getExtraneousGroupIdTable(deviceId).values());
+ }
+
+ @Override
+ public void pushGroupMetrics(DeviceId deviceId,
+ Collection<Group> groupEntries) {
+ boolean deviceInitialAuditStatus =
+ deviceInitialAuditStatus(deviceId);
+ Set<Group> southboundGroupEntries =
+ Sets.newHashSet(groupEntries);
+ Set<Group> storedGroupEntries =
+ Sets.newHashSet(getGroups(deviceId));
+ Set<Group> extraneousStoredEntries =
+ Sets.newHashSet(getExtraneousGroups(deviceId));
+
+ log.trace("pushGroupMetrics: Displaying all ({}) "
+ + "southboundGroupEntries for device {}",
+ southboundGroupEntries.size(),
+ deviceId);
+ for (Iterator<Group> it = southboundGroupEntries.iterator(); it.hasNext();) {
+ Group group = it.next();
+ log.trace("Group {} in device {}", group, deviceId);
+ }
+
+ log.trace("Displaying all ({}) stored group entries for device {}",
+ storedGroupEntries.size(),
+ deviceId);
+ for (Iterator<Group> it1 = storedGroupEntries.iterator();
+ it1.hasNext();) {
+ Group group = it1.next();
+ log.trace("Stored Group {} for device {}", group, deviceId);
+ }
+
+ for (Iterator<Group> it2 = southboundGroupEntries.iterator(); it2.hasNext();) {
+ Group group = it2.next();
+ if (storedGroupEntries.remove(group)) {
+ // we both have the group, let's update some info then.
+ log.trace("Group AUDIT: group {} exists "
+ + "in both planes for device {}",
+ group.id(), deviceId);
+ groupAdded(group);
+ it2.remove();
+ }
+ }
+ for (Group group : southboundGroupEntries) {
+ if (getGroup(group.deviceId(), group.id()) != null) {
+ // There is a group existing with the same id
+ // It is possible that group update is
+ // in progress while we got a stale info from switch
+ if (!storedGroupEntries.remove(getGroup(
+ group.deviceId(), group.id()))) {
+ log.warn("Group AUDIT: Inconsistent state:"
+ + "Group exists in ID based table while "
+ + "not present in key based table");
+ }
+ } else {
+ // there are groups in the switch that aren't in the store
+ log.trace("Group AUDIT: extraneous group {} exists "
+ + "in data plane for device {}",
+ group.id(), deviceId);
+ extraneousStoredEntries.remove(group);
+ extraneousGroup(group);
+ }
+ }
+ for (Group group : storedGroupEntries) {
+ // there are groups in the store that aren't in the switch
+ log.trace("Group AUDIT: group {} missing "
+ + "in data plane for device {}",
+ group.id(), deviceId);
+ groupMissing(group);
+ }
+ for (Group group : extraneousStoredEntries) {
+ // there are groups in the extraneous store that
+ // aren't in the switch
+ log.trace("Group AUDIT: clearing extransoeus group {} "
+ + "from store for device {}",
+ group.id(), deviceId);
+ removeExtraneousGroupEntry(group);
+ }
+
+ if (!deviceInitialAuditStatus) {
+ log.debug("Group AUDIT: Setting device {} initial "
+ + "AUDIT completed", deviceId);
+ deviceInitialAuditCompleted(deviceId, true);
+ }
+ }
+
+ private void groupMissing(Group group) {
+ switch (group.state()) {
+ case PENDING_DELETE:
+ log.debug("Group {} delete confirmation from device {}",
+ group, group.deviceId());
+ removeGroupEntry(group);
+ break;
+ case ADDED:
+ case PENDING_ADD:
+ case PENDING_UPDATE:
+ log.debug("Group {} is in store but not on device {}",
+ group, group.deviceId());
+ StoredGroupEntry existing = (groupEntriesById.get(
+ group.deviceId()) != null) ?
+ groupEntriesById.get(group.deviceId()).get(group.id()) :
+ null;
+ log.trace("groupMissing: group "
+ + "entry {} in device {} moving "
+ + "from {} to PENDING_ADD",
+ existing.id(),
+ existing.deviceId(),
+ existing.state());
+ existing.setState(Group.GroupState.PENDING_ADD);
+ notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+ group));
+ break;
+ default:
+ log.debug("Group {} has not been installed.", group);
+ break;
+ }
+ }
+
+ private void extraneousGroup(Group group) {
+ log.debug("Group {} is on device {} but not in store.",
+ group, group.deviceId());
+ addOrUpdateExtraneousGroupEntry(group);
+ }
+
+ private void groupAdded(Group group) {
+ log.trace("Group {} Added or Updated in device {}",
+ group, group.deviceId());
+ addOrUpdateGroupEntry(group);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java
new file mode 100644
index 00000000..dd6c8a58
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java
@@ -0,0 +1,482 @@
+/*
+ * 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.store.trivial;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+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.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+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.GroupOperation;
+import org.onosproject.net.group.GroupStore.UpdateType;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Test of the simple DeviceStore implementation.
+ */
+public class SimpleGroupStoreTest {
+
+ private SimpleGroupStore simpleGroupStore;
+ private final ApplicationId appId =
+ new DefaultApplicationId(2, "org.groupstore.test");
+
+ public static final DeviceId D1 = deviceId("of:1");
+
+ @Before
+ public void setUp() throws Exception {
+ simpleGroupStore = new SimpleGroupStore();
+ simpleGroupStore.activate();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ simpleGroupStore.deactivate();
+ }
+
+ private class InternalGroupStoreDelegate
+ implements GroupStoreDelegate {
+ private GroupId createdGroupId = null;
+ private GroupKey createdGroupKey;
+ private GroupBuckets createdBuckets;
+ private GroupEvent.Type expectedEvent;
+
+ public InternalGroupStoreDelegate(GroupKey key,
+ GroupBuckets buckets,
+ GroupEvent.Type expectedEvent) {
+ this.createdBuckets = buckets;
+ this.createdGroupKey = key;
+ this.expectedEvent = expectedEvent;
+ }
+ @Override
+ public void notify(GroupEvent event) {
+ assertEquals(expectedEvent, event.type());
+ assertEquals(Group.Type.SELECT, event.subject().type());
+ assertEquals(D1, event.subject().deviceId());
+ assertEquals(createdGroupKey, event.subject().appCookie());
+ assertEquals(createdBuckets.buckets(), event.subject().buckets().buckets());
+ if (expectedEvent == GroupEvent.Type.GROUP_ADD_REQUESTED) {
+ createdGroupId = event.subject().id();
+ assertEquals(Group.GroupState.PENDING_ADD,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_ADDED) {
+ createdGroupId = event.subject().id();
+ assertEquals(Group.GroupState.ADDED,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATED) {
+ createdGroupId = event.subject().id();
+ assertEquals(true,
+ event.subject().buckets().
+ buckets().containsAll(createdBuckets.buckets()));
+ assertEquals(true,
+ createdBuckets.buckets().
+ containsAll(event.subject().buckets().buckets()));
+ for (GroupBucket bucket:event.subject().buckets().buckets()) {
+ Optional<GroupBucket> matched = createdBuckets.buckets()
+ .stream()
+ .filter((expected) -> expected.equals(bucket))
+ .findFirst();
+ assertEquals(matched.get().packets(),
+ bucket.packets());
+ assertEquals(matched.get().bytes(),
+ bucket.bytes());
+ }
+ assertEquals(Group.GroupState.ADDED,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_REQUESTED) {
+ assertEquals(Group.GroupState.PENDING_UPDATE,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVE_REQUESTED) {
+ assertEquals(Group.GroupState.PENDING_DELETE,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVED) {
+ createdGroupId = event.subject().id();
+ assertEquals(Group.GroupState.PENDING_DELETE,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_ADD_FAILED) {
+ createdGroupId = event.subject().id();
+ assertEquals(Group.GroupState.PENDING_ADD,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_FAILED) {
+ createdGroupId = event.subject().id();
+ assertEquals(Group.GroupState.PENDING_UPDATE,
+ event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVE_FAILED) {
+ createdGroupId = event.subject().id();
+ assertEquals(Group.GroupState.PENDING_DELETE,
+ event.subject().state());
+ }
+ }
+
+ public void verifyGroupId(GroupId id) {
+ assertEquals(createdGroupId, id);
+ }
+ }
+
+ /**
+ * Tests group store operations. The following operations are tested:
+ * a)Tests device group audit completion status change
+ * b)Tests storeGroup operation
+ * c)Tests getGroupCount operation
+ * d)Tests getGroup operation
+ * e)Tests getGroups operation
+ * f)Tests addOrUpdateGroupEntry operation from southbound
+ * g)Tests updateGroupDescription for ADD operation from northbound
+ * h)Tests updateGroupDescription for REMOVE operation from northbound
+ * i)Tests deleteGroupDescription operation from northbound
+ * j)Tests removeGroupEntry operation from southbound
+ */
+ @Test
+ public void testGroupStoreOperations() {
+ // Set the Device AUDIT completed in the store
+ simpleGroupStore.deviceInitialAuditCompleted(D1, true);
+
+ // Testing storeGroup operation
+ GroupKey newKey = new DefaultGroupKey("group1".getBytes());
+ testStoreAndGetGroup(newKey);
+
+ // Testing addOrUpdateGroupEntry operation from southbound
+ GroupKey currKey = newKey;
+ testAddGroupEntryFromSB(currKey);
+
+ // Testing updateGroupDescription for ADD operation from northbound
+ newKey = new DefaultGroupKey("group1AddBuckets".getBytes());
+ testAddBuckets(currKey, newKey);
+
+ // Testing updateGroupDescription for REMOVE operation from northbound
+ currKey = newKey;
+ newKey = new DefaultGroupKey("group1RemoveBuckets".getBytes());
+ testRemoveBuckets(currKey, newKey);
+
+ // Testing addOrUpdateGroupEntry operation from southbound
+ currKey = newKey;
+ testUpdateGroupEntryFromSB(currKey);
+
+ // Testing deleteGroupDescription operation from northbound
+ testDeleteGroup(currKey);
+
+ // Testing removeGroupEntry operation from southbound
+ testRemoveGroupFromSB(currKey);
+ }
+
+ // Testing storeGroup operation
+ private void testStoreAndGetGroup(GroupKey key) {
+ PortNumber[] ports = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ List<PortNumber> outPorts = new ArrayList<PortNumber>();
+ outPorts.addAll(Arrays.asList(ports));
+
+ List<GroupBucket> buckets = 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));
+ buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupBuckets = new GroupBuckets(buckets);
+ GroupDescription groupDesc = new DefaultGroupDescription(
+ D1,
+ Group.Type.SELECT,
+ groupBuckets,
+ key,
+ null,
+ appId);
+ InternalGroupStoreDelegate checkStoreGroupDelegate =
+ new InternalGroupStoreDelegate(key,
+ groupBuckets,
+ GroupEvent.Type.GROUP_ADD_REQUESTED);
+ simpleGroupStore.setDelegate(checkStoreGroupDelegate);
+ // Testing storeGroup operation
+ simpleGroupStore.storeGroupDescription(groupDesc);
+
+ // Testing getGroupCount operation
+ assertEquals(1, simpleGroupStore.getGroupCount(D1));
+
+ // Testing getGroup operation
+ Group createdGroup = simpleGroupStore.getGroup(D1, key);
+ checkStoreGroupDelegate.verifyGroupId(createdGroup.id());
+
+ // Testing getGroups operation
+ Iterable<Group> createdGroups = simpleGroupStore.getGroups(D1);
+ int groupCount = 0;
+ for (Group group:createdGroups) {
+ checkStoreGroupDelegate.verifyGroupId(group.id());
+ groupCount++;
+ }
+ assertEquals(1, groupCount);
+ simpleGroupStore.unsetDelegate(checkStoreGroupDelegate);
+ }
+
+ // Testing addOrUpdateGroupEntry operation from southbound
+ private void testAddGroupEntryFromSB(GroupKey currKey) {
+ Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+
+ InternalGroupStoreDelegate addGroupEntryDelegate =
+ new InternalGroupStoreDelegate(currKey,
+ existingGroup.buckets(),
+ GroupEvent.Type.GROUP_ADDED);
+ simpleGroupStore.setDelegate(addGroupEntryDelegate);
+ simpleGroupStore.addOrUpdateGroupEntry(existingGroup);
+ simpleGroupStore.unsetDelegate(addGroupEntryDelegate);
+ }
+
+ // Testing addOrUpdateGroupEntry operation from southbound
+ private void testUpdateGroupEntryFromSB(GroupKey currKey) {
+ Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ int totalPkts = 0;
+ int totalBytes = 0;
+ List<GroupBucket> newBucketList = new ArrayList<GroupBucket>();
+ for (GroupBucket bucket:existingGroup.buckets().buckets()) {
+ StoredGroupBucketEntry newBucket =
+ (StoredGroupBucketEntry)
+ DefaultGroupBucket.createSelectGroupBucket(bucket.treatment());
+ newBucket.setPackets(10);
+ newBucket.setBytes(10 * 256 * 8);
+ totalPkts += 10;
+ totalBytes += 10 * 256 * 8;
+ newBucketList.add(newBucket);
+ }
+ GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+ Group updatedGroup = new DefaultGroup(existingGroup.id(),
+ existingGroup.deviceId(),
+ existingGroup.type(),
+ updatedBuckets);
+ ((StoredGroupEntry) updatedGroup).setPackets(totalPkts);
+ ((StoredGroupEntry) updatedGroup).setBytes(totalBytes);
+
+ InternalGroupStoreDelegate updateGroupEntryDelegate =
+ new InternalGroupStoreDelegate(currKey,
+ updatedBuckets,
+ GroupEvent.Type.GROUP_UPDATED);
+ simpleGroupStore.setDelegate(updateGroupEntryDelegate);
+ simpleGroupStore.addOrUpdateGroupEntry(updatedGroup);
+ simpleGroupStore.unsetDelegate(updateGroupEntryDelegate);
+ }
+
+ // Testing updateGroupDescription for ADD operation from northbound
+ private void testAddBuckets(GroupKey currKey, GroupKey addKey) {
+ Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ List<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ buckets.addAll(existingGroup.buckets().buckets());
+
+ PortNumber[] newNeighborPorts = {PortNumber.portNumber(41),
+ PortNumber.portNumber(42)};
+ List<PortNumber> newOutPorts = new ArrayList<PortNumber>();
+ newOutPorts.addAll(Collections.singletonList(newNeighborPorts[0]));
+
+ List<GroupBucket> toAddBuckets = new ArrayList<GroupBucket>();
+ for (PortNumber portNumber: newOutPorts) {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ tBuilder.setOutput(portNumber)
+ .setEthDst(MacAddress.valueOf("00:00:00:00:00:03"))
+ .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(106));
+ toAddBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets toAddGroupBuckets = new GroupBuckets(toAddBuckets);
+ buckets.addAll(toAddBuckets);
+ GroupBuckets updatedGroupBuckets = new GroupBuckets(buckets);
+ InternalGroupStoreDelegate updateGroupDescDelegate =
+ new InternalGroupStoreDelegate(addKey,
+ updatedGroupBuckets,
+ GroupEvent.Type.GROUP_UPDATE_REQUESTED);
+ simpleGroupStore.setDelegate(updateGroupDescDelegate);
+ simpleGroupStore.updateGroupDescription(D1,
+ currKey,
+ UpdateType.ADD,
+ toAddGroupBuckets,
+ addKey);
+ simpleGroupStore.unsetDelegate(updateGroupDescDelegate);
+ }
+
+ // Testing updateGroupDescription for REMOVE operation from northbound
+ private void testRemoveBuckets(GroupKey currKey, GroupKey removeKey) {
+ Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ List<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ buckets.addAll(existingGroup.buckets().buckets());
+
+ List<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>();
+
+ // There should be 4 buckets in the current group
+ toRemoveBuckets.add(buckets.remove(0));
+ toRemoveBuckets.add(buckets.remove(1));
+ GroupBuckets toRemoveGroupBuckets = new GroupBuckets(toRemoveBuckets);
+
+ GroupBuckets remainingGroupBuckets = new GroupBuckets(buckets);
+ InternalGroupStoreDelegate removeGroupDescDelegate =
+ new InternalGroupStoreDelegate(removeKey,
+ remainingGroupBuckets,
+ GroupEvent.Type.GROUP_UPDATE_REQUESTED);
+ simpleGroupStore.setDelegate(removeGroupDescDelegate);
+ simpleGroupStore.updateGroupDescription(D1,
+ currKey,
+ UpdateType.REMOVE,
+ toRemoveGroupBuckets,
+ removeKey);
+ simpleGroupStore.unsetDelegate(removeGroupDescDelegate);
+ }
+
+ // Testing deleteGroupDescription operation from northbound
+ private void testDeleteGroup(GroupKey currKey) {
+ Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ InternalGroupStoreDelegate deleteGroupDescDelegate =
+ new InternalGroupStoreDelegate(currKey,
+ existingGroup.buckets(),
+ GroupEvent.Type.GROUP_REMOVE_REQUESTED);
+ simpleGroupStore.setDelegate(deleteGroupDescDelegate);
+ simpleGroupStore.deleteGroupDescription(D1, currKey);
+ simpleGroupStore.unsetDelegate(deleteGroupDescDelegate);
+ }
+
+ // Testing removeGroupEntry operation from southbound
+ private void testRemoveGroupFromSB(GroupKey currKey) {
+ Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ InternalGroupStoreDelegate removeGroupEntryDelegate =
+ new InternalGroupStoreDelegate(currKey,
+ existingGroup.buckets(),
+ GroupEvent.Type.GROUP_REMOVED);
+ simpleGroupStore.setDelegate(removeGroupEntryDelegate);
+ simpleGroupStore.removeGroupEntry(existingGroup);
+
+ // Testing getGroup operation
+ existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ assertEquals(null, existingGroup);
+ assertEquals(0, Iterables.size(simpleGroupStore.getGroups(D1)));
+ assertEquals(0, simpleGroupStore.getGroupCount(D1));
+
+ simpleGroupStore.unsetDelegate(removeGroupEntryDelegate);
+ }
+
+ @Test
+ public void testGroupOperationFailure() {
+
+ simpleGroupStore.deviceInitialAuditCompleted(D1, true);
+
+ ApplicationId appId =
+ new DefaultApplicationId(2, "org.groupstore.test");
+ GroupKey key = new DefaultGroupKey("group1".getBytes());
+ PortNumber[] ports = {PortNumber.portNumber(31),
+ PortNumber.portNumber(32)};
+ List<PortNumber> outPorts = new ArrayList<PortNumber>();
+ outPorts.add(ports[0]);
+ outPorts.add(ports[1]);
+
+ List<GroupBucket> buckets = 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));
+ buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+ tBuilder.build()));
+ }
+ GroupBuckets groupBuckets = new GroupBuckets(buckets);
+ GroupDescription groupDesc = new DefaultGroupDescription(
+ D1,
+ Group.Type.SELECT,
+ groupBuckets,
+ key,
+ null,
+ appId);
+ InternalGroupStoreDelegate checkStoreGroupDelegate =
+ new InternalGroupStoreDelegate(key,
+ groupBuckets,
+ GroupEvent.Type.GROUP_ADD_REQUESTED);
+ simpleGroupStore.setDelegate(checkStoreGroupDelegate);
+ // Testing storeGroup operation
+ simpleGroupStore.storeGroupDescription(groupDesc);
+ simpleGroupStore.unsetDelegate(checkStoreGroupDelegate);
+
+ // Testing Group add operation failure
+ Group createdGroup = simpleGroupStore.getGroup(D1, key);
+ checkStoreGroupDelegate.verifyGroupId(createdGroup.id());
+
+ GroupOperation groupAddOp = GroupOperation.
+ createAddGroupOperation(createdGroup.id(),
+ createdGroup.type(),
+ createdGroup.buckets());
+ InternalGroupStoreDelegate checkGroupAddFailureDelegate =
+ new InternalGroupStoreDelegate(key,
+ groupBuckets,
+ GroupEvent.Type.GROUP_ADD_FAILED);
+ simpleGroupStore.setDelegate(checkGroupAddFailureDelegate);
+ simpleGroupStore.groupOperationFailed(D1, groupAddOp);
+
+
+ // Testing Group modify operation failure
+ simpleGroupStore.unsetDelegate(checkGroupAddFailureDelegate);
+ GroupOperation groupModOp = GroupOperation.
+ createModifyGroupOperation(createdGroup.id(),
+ createdGroup.type(),
+ createdGroup.buckets());
+ InternalGroupStoreDelegate checkGroupModFailureDelegate =
+ new InternalGroupStoreDelegate(key,
+ groupBuckets,
+ GroupEvent.Type.GROUP_UPDATE_FAILED);
+ simpleGroupStore.setDelegate(checkGroupModFailureDelegate);
+ simpleGroupStore.groupOperationFailed(D1, groupModOp);
+
+ // Testing Group modify operation failure
+ simpleGroupStore.unsetDelegate(checkGroupModFailureDelegate);
+ GroupOperation groupDelOp = GroupOperation.
+ createDeleteGroupOperation(createdGroup.id(),
+ createdGroup.type());
+ InternalGroupStoreDelegate checkGroupDelFailureDelegate =
+ new InternalGroupStoreDelegate(key,
+ groupBuckets,
+ GroupEvent.Type.GROUP_REMOVE_FAILED);
+ simpleGroupStore.setDelegate(checkGroupDelFailureDelegate);
+ simpleGroupStore.groupOperationFailed(D1, groupDelOp);
+ }
+}
+
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
new file mode 100644
index 00000000..f5604f68
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
@@ -0,0 +1,293 @@
+/*
+ * 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.store.trivial;
+
+import static org.onosproject.net.DefaultAnnotations.merge;
+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 static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+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.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostStore;
+import org.onosproject.net.host.HostStoreDelegate;
+import org.onosproject.net.host.PortAddresses;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.slf4j.Logger;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+
+// TODO: multi-provider, annotation not supported.
+/**
+ * Manages inventory of end-station hosts using trivial in-memory
+ * implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleHostStore
+ extends AbstractStore<HostEvent, HostStoreDelegate>
+ implements HostStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // Host inventory
+ private final Map<HostId, StoredHost> hosts = new ConcurrentHashMap<>(2000000, 0.75f, 16);
+
+ // Hosts tracked by their location
+ private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
+
+ private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+ Multimaps.synchronizedSetMultimap(
+ HashMultimap.<ConnectPoint, PortAddresses>create());
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
+ HostDescription hostDescription) {
+ StoredHost host = hosts.get(hostId);
+ if (host == null) {
+ return createHost(providerId, hostId, hostDescription);
+ }
+ return updateHost(providerId, host, hostDescription);
+ }
+
+ // creates a new host and sends HOST_ADDED
+ private HostEvent createHost(ProviderId providerId, HostId hostId,
+ HostDescription descr) {
+ StoredHost newhost = new StoredHost(providerId, hostId,
+ descr.hwAddress(),
+ descr.vlan(),
+ descr.location(),
+ ImmutableSet.copyOf(descr.ipAddress()),
+ descr.annotations());
+ synchronized (this) {
+ hosts.put(hostId, newhost);
+ locations.put(descr.location(), newhost);
+ }
+ return new HostEvent(HOST_ADDED, newhost);
+ }
+
+ // checks for type of update to host, sends appropriate event
+ private HostEvent updateHost(ProviderId providerId, StoredHost host,
+ HostDescription descr) {
+ HostEvent event;
+ if (!host.location().equals(descr.location())) {
+ host.setLocation(descr.location());
+ return new HostEvent(HOST_MOVED, host);
+ }
+
+ if (host.ipAddresses().containsAll(descr.ipAddress()) &&
+ descr.annotations().keys().isEmpty()) {
+ return null;
+ }
+
+ Set<IpAddress> addresses = new HashSet<>(host.ipAddresses());
+ addresses.addAll(descr.ipAddress());
+ Annotations annotations = merge((DefaultAnnotations) host.annotations(),
+ descr.annotations());
+ StoredHost updated = new StoredHost(providerId, host.id(),
+ host.mac(), host.vlan(),
+ descr.location(), addresses,
+ annotations);
+ event = new HostEvent(HOST_UPDATED, updated);
+ synchronized (this) {
+ hosts.put(host.id(), updated);
+ locations.remove(host.location(), host);
+ locations.put(updated.location(), updated);
+ }
+ return event;
+ }
+
+ @Override
+ public HostEvent removeHost(HostId hostId) {
+ synchronized (this) {
+ Host host = hosts.remove(hostId);
+ if (host != null) {
+ locations.remove((host.location()), host);
+ return new HostEvent(HOST_REMOVED, host);
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public int getHostCount() {
+ return hosts.size();
+ }
+
+ @Override
+ public Iterable<Host> getHosts() {
+ return ImmutableSet.<Host>copyOf(hosts.values());
+ }
+
+ @Override
+ public Host getHost(HostId hostId) {
+ return hosts.get(hostId);
+ }
+
+ @Override
+ public Set<Host> getHosts(VlanId vlanId) {
+ Set<Host> vlanset = new HashSet<>();
+ for (Host h : hosts.values()) {
+ if (h.vlan().equals(vlanId)) {
+ vlanset.add(h);
+ }
+ }
+ return vlanset;
+ }
+
+ @Override
+ public Set<Host> getHosts(MacAddress mac) {
+ Set<Host> macset = new HashSet<>();
+ for (Host h : hosts.values()) {
+ if (h.mac().equals(mac)) {
+ macset.add(h);
+ }
+ }
+ return macset;
+ }
+
+ @Override
+ public Set<Host> getHosts(IpAddress ip) {
+ Set<Host> ipset = new HashSet<>();
+ for (Host h : hosts.values()) {
+ if (h.ipAddresses().contains(ip)) {
+ ipset.add(h);
+ }
+ }
+ return ipset;
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+ return ImmutableSet.copyOf(locations.get(connectPoint));
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(DeviceId deviceId) {
+ Set<Host> hostset = new HashSet<>();
+ for (ConnectPoint p : locations.keySet()) {
+ if (p.deviceId().equals(deviceId)) {
+ hostset.addAll(locations.get(p));
+ }
+ }
+ return hostset;
+ }
+
+ @Override
+ public void updateAddressBindings(PortAddresses addresses) {
+ portAddresses.put(addresses.connectPoint(), addresses);
+ }
+
+ @Override
+ public void removeAddressBindings(PortAddresses addresses) {
+ portAddresses.remove(addresses.connectPoint(), addresses);
+ }
+
+ @Override
+ public void clearAddressBindings(ConnectPoint connectPoint) {
+ portAddresses.removeAll(connectPoint);
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindings() {
+ synchronized (portAddresses) {
+ return ImmutableSet.copyOf(portAddresses.values());
+ }
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
+ synchronized (portAddresses) {
+ Set<PortAddresses> addresses = portAddresses.get(connectPoint);
+
+ if (addresses == null) {
+ return Collections.emptySet();
+ } else {
+ return ImmutableSet.copyOf(addresses);
+ }
+ }
+ }
+
+ // Auxiliary extension to allow location to mutate.
+ private static final class StoredHost extends DefaultHost {
+ private HostLocation location;
+
+ /**
+ * Creates an end-station host using the supplied information.
+ *
+ * @param providerId provider identity
+ * @param id host identifier
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param location host location
+ * @param ips host IP addresses
+ * @param annotations optional key/value annotations
+ */
+ public StoredHost(ProviderId providerId, HostId id,
+ MacAddress mac, VlanId vlan, HostLocation location,
+ Set<IpAddress> ips, Annotations... annotations) {
+ super(providerId, id, mac, vlan, location, ips, annotations);
+ this.location = location;
+ }
+
+ void setLocation(HostLocation location) {
+ this.location = location;
+ }
+
+ @Override
+ public HostLocation location() {
+ return location;
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java
new file mode 100644
index 00000000..3f7e563a
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.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.store.trivial;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.IdBlock;
+import org.onosproject.core.IdBlockStore;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Simple implementation of id block store.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleIdBlockStore implements IdBlockStore {
+
+ private static final long DEFAULT_BLOCK_SIZE = 0x1000L;
+
+ private final Map<String, AtomicLong> topicBlocks = new ConcurrentHashMap<>();
+
+ @Override
+ public synchronized IdBlock getIdBlock(String topic) {
+ AtomicLong blockGenerator = topicBlocks.get(topic);
+ if (blockGenerator == null) {
+ blockGenerator = new AtomicLong(0);
+ topicBlocks.put(topic, blockGenerator);
+ }
+ Long blockBase = blockGenerator.getAndAdd(DEFAULT_BLOCK_SIZE);
+ return new IdBlock(blockBase, DEFAULT_BLOCK_SIZE);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java
new file mode 100644
index 00000000..9f959663
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java
@@ -0,0 +1,212 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.collect.Lists;
+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.Service;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+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.store.AbstractStore;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.PURGE_REQ;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple single-instance implementation of the intent store.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleIntentStore
+ extends AbstractStore<IntentEvent, IntentStoreDelegate>
+ implements IntentStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final Map<Key, IntentData> current = Maps.newConcurrentMap();
+ private final Map<Key, IntentData> pending = Maps.newConcurrentMap();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public long getIntentCount() {
+ return current.size();
+ }
+
+ @Override
+ public Iterable<Intent> getIntents() {
+ return current.values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getIntentData(boolean localOnly, long olderThan) {
+ if (localOnly || olderThan > 0) {
+ long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
+ final SystemClockTimestamp time = new SystemClockTimestamp(older);
+ return current.values().stream()
+ .filter(data -> data.version().isOlderThan(time) &&
+ (!localOnly || isMaster(data.key())))
+ .collect(Collectors.toList());
+ }
+ return Lists.newArrayList(current.values());
+ }
+
+ @Override
+ public IntentState getIntentState(Key intentKey) {
+ IntentData data = current.get(intentKey);
+ return (data != null) ? data.state() : null;
+ }
+
+ @Override
+ public List<Intent> getInstallableIntents(Key intentKey) {
+ IntentData data = current.get(intentKey);
+ if (data != null) {
+ return data.installables();
+ }
+ return null;
+ }
+
+ @Override
+ public void write(IntentData newData) {
+ checkNotNull(newData);
+
+ synchronized (this) {
+ // TODO this could be refactored/cleaned up
+ IntentData currentData = current.get(newData.key());
+ IntentData pendingData = pending.get(newData.key());
+
+ if (IntentData.isUpdateAcceptable(currentData, newData)) {
+ if (pendingData != null) {
+ if (pendingData.state() == PURGE_REQ) {
+ current.remove(newData.key(), newData);
+ } else {
+ current.put(newData.key(), new IntentData(newData));
+ }
+
+ if (pendingData.version().compareTo(newData.version()) <= 0) {
+ // pendingData version is less than or equal to newData's
+ // Note: a new update for this key could be pending (it's version will be greater)
+ pending.remove(newData.key());
+ }
+ }
+ notifyDelegateIfNotNull(IntentEvent.getEvent(newData));
+ }
+ }
+ }
+
+ private void notifyDelegateIfNotNull(IntentEvent event) {
+ if (event != null) {
+ notifyDelegate(event);
+ }
+ }
+
+ @Override
+ public void batchWrite(Iterable<IntentData> updates) {
+ for (IntentData data : updates) {
+ write(data);
+ }
+ }
+
+ @Override
+ public Intent getIntent(Key key) {
+ IntentData data = current.get(key);
+ return (data != null) ? data.intent() : null;
+ }
+
+ @Override
+ public IntentData getIntentData(Key key) {
+ IntentData currentData = current.get(key);
+ if (currentData == null) {
+ return null;
+ }
+ return new IntentData(currentData);
+ }
+
+ @Override
+ public void addPending(IntentData data) {
+ if (data.version() == null) { // recompiled intents will already have a version
+ data.setVersion(new SystemClockTimestamp());
+ }
+ synchronized (this) {
+ IntentData existingData = pending.get(data.key());
+ if (existingData == null ||
+ // existing version is strictly less than data's version
+ // Note: if they are equal, we already have the update
+ // TODO maybe we should still make this <= to be safe?
+ existingData.version().compareTo(data.version()) < 0) {
+ pending.put(data.key(), data);
+ checkNotNull(delegate, "Store delegate is not set")
+ .process(new IntentData(data));
+ notifyDelegateIfNotNull(IntentEvent.getEvent(data));
+ } else {
+ log.debug("IntentData {} is older than existing: {}",
+ data, existingData);
+ }
+ //TODO consider also checking the current map at this point
+ }
+ }
+
+ @Override
+ public boolean isMaster(Key intentKey) {
+ return true;
+ }
+
+ @Override
+ public Iterable<Intent> getPending() {
+ return pending.values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData() {
+ return Lists.newArrayList(pending.values());
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData(boolean localOnly, long olderThan) {
+ long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
+ final SystemClockTimestamp time = new SystemClockTimestamp(older);
+ return pending.values().stream()
+ .filter(data -> data.version().isOlderThan(time) &&
+ (!localOnly || isMaster(data.key())))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java
new file mode 100644
index 00000000..194ffec1
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java
@@ -0,0 +1,135 @@
+/*
+ * 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.store.trivial;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+
+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.cluster.ClusterService;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEvent.Type;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+
+/**
+ * A trivial implementation of the leadership service.
+ * <p>
+ * The service is not distributed, so it can assume there's a single leadership
+ * contender. This contender is always granted leadership whenever it asks.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleLeadershipManager implements LeadershipService {
+
+ private Set<LeadershipEventListener> listeners = new CopyOnWriteArraySet<>();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ClusterService clusterService;
+
+ private Map<String, Boolean> elections = new ConcurrentHashMap<>();
+
+ @Override
+ public NodeId getLeader(String path) {
+ return elections.get(path) ? clusterService.getLocalNode().id() : null;
+ }
+
+ @Override
+ public Leadership getLeadership(String path) {
+ checkArgument(path != null);
+ return elections.get(path) ? new Leadership(path, clusterService.getLocalNode().id(), 0, 0) : null;
+ }
+
+ @Override
+ public Set<String> ownedTopics(NodeId nodeId) {
+ checkArgument(nodeId != null);
+ return elections.entrySet()
+ .stream()
+ .filter(Entry::getValue)
+ .map(Entry::getKey)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public CompletableFuture<Leadership> runForLeadership(String path) {
+ elections.put(path, true);
+ for (LeadershipEventListener listener : listeners) {
+ listener.event(new LeadershipEvent(Type.LEADER_ELECTED,
+ new Leadership(path, clusterService.getLocalNode().id(), 0, 0)));
+ }
+ return CompletableFuture.completedFuture(new Leadership(path, clusterService.getLocalNode().id(), 0, 0));
+ }
+
+ @Override
+ public CompletableFuture<Void> withdraw(String path) {
+ elections.remove(path);
+ for (LeadershipEventListener listener : listeners) {
+ listener.event(new LeadershipEvent(Type.LEADER_BOOTED,
+ new Leadership(path, clusterService.getLocalNode().id(), 0, 0)));
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public Map<String, Leadership> getLeaderBoard() {
+ //FIXME
+ throw new UnsupportedOperationException("I don't know what to do." +
+ " I wish you luck.");
+ }
+
+ @Override
+ public void addListener(LeadershipEventListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(LeadershipEventListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public Map<String, List<NodeId>> getCandidates() {
+ return null;
+ }
+
+ @Override
+ public List<NodeId> getCandidates(String path) {
+ return null;
+ }
+
+ @Override
+ public boolean stepdown(String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean makeTopCandidate(String path, NodeId nodeId) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java
new file mode 100644
index 00000000..58b446cf
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java
@@ -0,0 +1,286 @@
+/*
+ * 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.store.trivial;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+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.apache.felix.scr.annotations.Service;
+import org.onlab.util.Bandwidth;
+import org.onlab.util.PositionalParameterStringFormatter;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.BandwidthResourceAllocation;
+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.LinkResourceEvent;
+import org.onosproject.net.resource.link.LinkResourceStore;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceAllocationException;
+import org.onosproject.net.resource.ResourceType;
+import org.slf4j.Logger;
+
+import com.google.common.collect.ImmutableList;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages link resources using trivial in-memory structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleLinkResourceStore implements LinkResourceStore {
+ private static final BandwidthResource DEFAULT_BANDWIDTH = new BandwidthResource(Bandwidth.mbps(1_000));
+ private final Logger log = getLogger(getClass());
+
+ private Map<IntentId, LinkResourceAllocations> linkResourceAllocationsMap;
+ private Map<Link, Set<LinkResourceAllocations>> allocatedResources;
+ private Map<Link, Set<ResourceAllocation>> freeResources;
+
+ @Activate
+ public void activate() {
+ linkResourceAllocationsMap = new HashMap<>();
+ allocatedResources = new HashMap<>();
+ freeResources = new HashMap<>();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ /**
+ * Returns free resources for a given link obtaining from topology
+ * information.
+ *
+ * @param link the target link
+ * @return free resources
+ */
+ private synchronized Set<ResourceAllocation> readOriginalFreeResources(Link link) {
+ Annotations annotations = link.annotations();
+ Set<ResourceAllocation> allocations = new HashSet<>();
+
+ try {
+ int waves = Integer.parseInt(annotations.value(AnnotationKeys.OPTICAL_WAVES));
+ for (int i = 1; i <= waves; i++) {
+ allocations.add(new LambdaResourceAllocation(LambdaResource.valueOf(i)));
+ }
+ } catch (NumberFormatException e) {
+ log.debug("No optical.wave annotation on link %s", link);
+ }
+
+ BandwidthResource bandwidth = DEFAULT_BANDWIDTH;
+ try {
+ bandwidth = new BandwidthResource(
+ Bandwidth.mbps((Double.parseDouble(annotations.value(AnnotationKeys.BANDWIDTH)))));
+ } catch (NumberFormatException e) {
+ log.debug("No bandwidth annotation on link %s", link);
+ }
+ allocations.add(
+ new BandwidthResourceAllocation(bandwidth));
+ return allocations;
+ }
+
+ /**
+ * Finds and returns {@link BandwidthResourceAllocation} object from a given
+ * set.
+ *
+ * @param freeRes a set of ResourceAllocation object.
+ * @return {@link BandwidthResourceAllocation} object if found, otherwise
+ * {@link BandwidthResourceAllocation} object with 0 bandwidth
+ *
+ */
+ private synchronized BandwidthResourceAllocation getBandwidth(
+ Set<ResourceAllocation> freeRes) {
+ for (ResourceAllocation res : freeRes) {
+ if (res.type() == ResourceType.BANDWIDTH) {
+ return (BandwidthResourceAllocation) res;
+ }
+ }
+ return new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(0)));
+ }
+
+ /**
+ * Subtracts given resources from free resources for given link.
+ *
+ * @param link the target link
+ * @param allocations the resources to be subtracted
+ */
+ private synchronized void subtractFreeResources(Link link,
+ LinkResourceAllocations allocations) {
+ // TODO Use lock or version for updating freeResources.
+ checkNotNull(link);
+ Set<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
+ Set<ResourceAllocation> subRes = allocations.getResourceAllocation(link);
+ for (ResourceAllocation res : subRes) {
+ switch (res.type()) {
+ case BANDWIDTH:
+ BandwidthResourceAllocation ba = getBandwidth(freeRes);
+ double requestedBandwidth =
+ ((BandwidthResourceAllocation) res).bandwidth().toDouble();
+ double newBandwidth = ba.bandwidth().toDouble() - requestedBandwidth;
+ if (newBandwidth < 0.0) {
+ throw new ResourceAllocationException(
+ PositionalParameterStringFormatter.format(
+ "Unable to allocate bandwidth for link {} "
+ + "requested amount is {} current allocation is {}",
+ link,
+ requestedBandwidth,
+ ba));
+ }
+ freeRes.remove(ba);
+ freeRes.add(new BandwidthResourceAllocation(
+ new BandwidthResource(Bandwidth.bps(newBandwidth))));
+ break;
+ case LAMBDA:
+ final boolean lambdaAvailable = freeRes.remove(res);
+ if (!lambdaAvailable) {
+ int requestedLambda =
+ ((LambdaResourceAllocation) res).lambda().toInt();
+ throw new ResourceAllocationException(
+ PositionalParameterStringFormatter.format(
+ "Unable to allocate lambda for link {} lambda is {}",
+ link,
+ requestedLambda));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ freeResources.put(link, freeRes);
+
+ }
+
+ /**
+ * Adds given resources to free resources for given link.
+ *
+ * @param link the target link
+ * @param allocations the resources to be added
+ */
+ private synchronized void addFreeResources(Link link,
+ LinkResourceAllocations allocations) {
+ // TODO Use lock or version for updating freeResources.
+ Set<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
+ Set<ResourceAllocation> addRes = allocations.getResourceAllocation(link);
+ for (ResourceAllocation res : addRes) {
+ switch (res.type()) {
+ case BANDWIDTH:
+ BandwidthResourceAllocation ba = getBandwidth(freeRes);
+ double requestedBandwidth =
+ ((BandwidthResourceAllocation) res).bandwidth().toDouble();
+ double newBandwidth = ba.bandwidth().toDouble() + requestedBandwidth;
+ freeRes.remove(ba);
+ freeRes.add(new BandwidthResourceAllocation(
+ new BandwidthResource(Bandwidth.bps(newBandwidth))));
+ break;
+ case LAMBDA:
+ checkState(freeRes.add(res));
+ break;
+ default:
+ break;
+ }
+ }
+ freeResources.put(link, freeRes);
+ }
+
+ @Override
+ public synchronized Set<ResourceAllocation> getFreeResources(Link link) {
+ checkNotNull(link);
+ Set<ResourceAllocation> freeRes = freeResources.get(link);
+ if (freeRes == null) {
+ freeRes = readOriginalFreeResources(link);
+ }
+
+ return freeRes;
+ }
+
+ @Override
+ public synchronized void allocateResources(LinkResourceAllocations allocations) {
+ checkNotNull(allocations);
+ linkResourceAllocationsMap.put(allocations.intentId(), allocations);
+ for (Link link : allocations.links()) {
+ subtractFreeResources(link, allocations);
+ Set<LinkResourceAllocations> linkAllocs = allocatedResources.get(link);
+ if (linkAllocs == null) {
+ linkAllocs = new HashSet<>();
+ }
+ linkAllocs.add(allocations);
+ allocatedResources.put(link, linkAllocs);
+ }
+ }
+
+ @Override
+ public synchronized LinkResourceEvent releaseResources(LinkResourceAllocations allocations) {
+ checkNotNull(allocations);
+ linkResourceAllocationsMap.remove(allocations.intentId());
+ for (Link link : allocations.links()) {
+ addFreeResources(link, allocations);
+ Set<LinkResourceAllocations> linkAllocs = allocatedResources.get(link);
+ if (linkAllocs == null) {
+ log.error("Missing resource allocation.");
+ } else {
+ linkAllocs.remove(allocations);
+ }
+ allocatedResources.put(link, linkAllocs);
+ }
+
+ final List<LinkResourceAllocations> releasedResources =
+ ImmutableList.of(allocations);
+
+ return new LinkResourceEvent(
+ LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE,
+ releasedResources);
+ }
+
+ @Override
+ public synchronized LinkResourceAllocations getAllocations(IntentId intentId) {
+ checkNotNull(intentId);
+ return linkResourceAllocationsMap.get(intentId);
+ }
+
+ @Override
+ public synchronized Iterable<LinkResourceAllocations> getAllocations(Link link) {
+ checkNotNull(link);
+ Set<LinkResourceAllocations> result = allocatedResources.get(link);
+ if (result == null) {
+ result = Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ @Override
+ public synchronized Iterable<LinkResourceAllocations> getAllocations() {
+ return Collections.unmodifiableCollection(linkResourceAllocationsMap.values());
+ }
+
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java
new file mode 100644
index 00000000..238e75d0
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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.store.trivial;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.BandwidthResourceAllocation;
+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.LinkResourceStore;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceAllocationException;
+import org.onosproject.net.resource.ResourceRequest;
+import org.onosproject.net.resource.ResourceType;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Test of the simple LinkResourceStore implementation.
+ */
+public class SimpleLinkResourceStoreTest {
+
+ private LinkResourceStore store;
+ private SimpleLinkResourceStore simpleStore;
+ private Link link1;
+ private Link link2;
+ private Link link3;
+
+ /**
+ * Returns {@link Link} object.
+ *
+ * @param dev1 source device
+ * @param port1 source port
+ * @param dev2 destination device
+ * @param port2 destination port
+ * @return created {@link Link} object
+ */
+ private static Link newLink(String dev1, int port1, String dev2, int port2) {
+ Annotations annotations = DefaultAnnotations.builder()
+ .set(AnnotationKeys.OPTICAL_WAVES, "80")
+ .set(AnnotationKeys.BANDWIDTH, "1000")
+ .build();
+ return new DefaultLink(
+ new ProviderId("of", "foo"),
+ new ConnectPoint(deviceId(dev1), portNumber(port1)),
+ new ConnectPoint(deviceId(dev2), portNumber(port2)),
+ DIRECT, annotations);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ simpleStore = new SimpleLinkResourceStore();
+ simpleStore.activate();
+ store = simpleStore;
+
+ link1 = newLink("of:1", 1, "of:2", 2);
+ link2 = newLink("of:2", 1, "of:3", 2);
+ link3 = newLink("of:3", 1, "of:4", 2);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ simpleStore.deactivate();
+ }
+
+ /**
+ * Tests constructor and activate method.
+ */
+ @Test
+ public void testConstructorAndActivate() {
+ final Iterable<LinkResourceAllocations> allAllocations = store.getAllocations();
+ assertNotNull(allAllocations);
+ assertFalse(allAllocations.iterator().hasNext());
+
+ final Iterable<LinkResourceAllocations> linkAllocations =
+ store.getAllocations(link1);
+ assertNotNull(linkAllocations);
+ assertFalse(linkAllocations.iterator().hasNext());
+
+ final Set<ResourceAllocation> res = store.getFreeResources(link2);
+ assertNotNull(res);
+ }
+
+ /**
+ * Picks up and returns one of bandwidth allocations from a given set.
+ *
+ * @param resources the set of {@link ResourceAllocation}s
+ * @return {@link BandwidthResourceAllocation} object if found, null
+ * otherwise
+ */
+ private BandwidthResourceAllocation getBandwidthObj(Set<ResourceAllocation> resources) {
+ for (ResourceAllocation res : resources) {
+ if (res.type() == ResourceType.BANDWIDTH) {
+ return ((BandwidthResourceAllocation) res);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns all lambda allocations from a given set.
+ *
+ * @param resources the set of {@link ResourceAllocation}s
+ * @return a set of {@link LambdaResourceAllocation} objects
+ */
+ private Set<LambdaResourceAllocation> getLambdaObjs(Set<ResourceAllocation> resources) {
+ Set<LambdaResourceAllocation> lambdaResources = new HashSet<>();
+ for (ResourceAllocation res : resources) {
+ if (res.type() == ResourceType.LAMBDA) {
+ lambdaResources.add((LambdaResourceAllocation) res);
+ }
+ }
+ return lambdaResources;
+ }
+
+ /**
+ * Tests initial free bandwidth for a link.
+ */
+ @Test
+ public void testInitialBandwidth() {
+ final Set<ResourceAllocation> freeRes = store.getFreeResources(link1);
+ assertNotNull(freeRes);
+
+ final BandwidthResourceAllocation alloc = getBandwidthObj(freeRes);
+ assertNotNull(alloc);
+
+ assertEquals(new BandwidthResource(Bandwidth.mbps(1000.0)), alloc.bandwidth());
+ }
+
+ /**
+ * Tests initial free lambda for a link.
+ */
+ @Test
+ public void testInitialLambdas() {
+ final Set<ResourceAllocation> freeRes = store.getFreeResources(link3);
+ assertNotNull(freeRes);
+
+ final Set<LambdaResourceAllocation> res = getLambdaObjs(freeRes);
+ assertNotNull(res);
+ assertEquals(80, res.size());
+ }
+
+ public static class MockLinkResourceBandwidthAllocations implements LinkResourceAllocations {
+ final double allocationAmount;
+
+ MockLinkResourceBandwidthAllocations(Double allocationAmount) {
+ this.allocationAmount = allocationAmount;
+ }
+ @Override
+ public Set<ResourceAllocation> getResourceAllocation(Link link) {
+ final ResourceAllocation allocation =
+ new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(allocationAmount)));
+ final Set<ResourceAllocation> allocations = new HashSet<>();
+ allocations.add(allocation);
+ return allocations;
+ }
+
+ @Override
+ public IntentId intentId() {
+ return null;
+ }
+
+ @Override
+ public Collection<Link> links() {
+ return ImmutableSet.of(newLink("of:1", 1, "of:2", 2));
+ }
+
+ @Override
+ public Set<ResourceRequest> resources() {
+ return null;
+ }
+
+ @Override
+ public ResourceType type() {
+ return null;
+ }
+ }
+
+ public static class MockLinkResourceLambdaAllocations implements LinkResourceAllocations {
+ final int allocatedLambda;
+
+ MockLinkResourceLambdaAllocations(int allocatedLambda) {
+ this.allocatedLambda = allocatedLambda;
+ }
+ @Override
+ public Set<ResourceAllocation> getResourceAllocation(Link link) {
+ final ResourceAllocation allocation =
+ new LambdaResourceAllocation(LambdaResource.valueOf(allocatedLambda));
+ final Set<ResourceAllocation> allocations = new HashSet<>();
+ allocations.add(allocation);
+ return allocations;
+ }
+
+ @Override
+ public IntentId intentId() {
+ return null;
+ }
+
+ @Override
+ public Collection<Link> links() {
+ return ImmutableSet.of(newLink("of:1", 1, "of:2", 2));
+ }
+
+ @Override
+ public Set<ResourceRequest> resources() {
+ return null;
+ }
+
+ @Override
+ public ResourceType type() {
+ return null;
+ }
+ }
+
+ /**
+ * Tests a successful bandwidth allocation.
+ */
+ @Test
+ public void testSuccessfulBandwidthAllocation() {
+ final LinkResourceAllocations allocations =
+ new MockLinkResourceBandwidthAllocations(900.0);
+ store.allocateResources(allocations);
+ }
+
+ /**
+ * Tests an unsuccessful bandwidth allocation.
+ */
+ @Test
+ public void testUnsuccessfulBandwidthAllocation() {
+ final LinkResourceAllocations allocations =
+ new MockLinkResourceBandwidthAllocations(2000000000.0);
+ boolean gotException = false;
+ try {
+ store.allocateResources(allocations);
+ } catch (ResourceAllocationException rae) {
+ assertEquals(true, rae.getMessage().contains("Unable to allocate bandwidth for link"));
+ gotException = true;
+ }
+ assertEquals(true, gotException);
+ }
+
+ /**
+ * Tests a successful lambda allocation.
+ */
+ @Test
+ public void testSuccessfulLambdaAllocation() {
+ final LinkResourceAllocations allocations =
+ new MockLinkResourceLambdaAllocations(1);
+ store.allocateResources(allocations);
+ }
+
+ /**
+ * Tests an unsuccessful lambda allocation.
+ */
+ @Test
+ public void testUnsuccessfulLambdaAllocation() {
+ final LinkResourceAllocations allocations =
+ new MockLinkResourceLambdaAllocations(1);
+ store.allocateResources(allocations);
+
+ boolean gotException = false;
+
+ try {
+ store.allocateResources(allocations);
+ } catch (ResourceAllocationException rae) {
+ assertEquals(true, rae.getMessage().contains("Unable to allocate lambda for link"));
+ gotException = true;
+ }
+ assertEquals(true, gotException);
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java
new file mode 100644
index 00000000..d0be2b1f
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java
@@ -0,0 +1,366 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+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.Service;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.AnnotationsUtil;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkStore;
+import org.onosproject.net.link.LinkStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.base.Verify.verifyNotNull;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.onosproject.net.DefaultAnnotations.union;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.State.INACTIVE;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.Link.Type.INDIRECT;
+import static org.onosproject.net.LinkKey.linkKey;
+import static org.onosproject.net.link.LinkEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of infrastructure links using trivial in-memory structures
+ * implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleLinkStore
+ extends AbstractStore<LinkEvent, LinkStoreDelegate>
+ implements LinkStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // Link inventory
+ private final ConcurrentMap<LinkKey, Map<ProviderId, LinkDescription>>
+ linkDescs = new ConcurrentHashMap<>();
+
+ // Link instance cache
+ private final ConcurrentMap<LinkKey, Link> links = new ConcurrentHashMap<>();
+
+ // Egress and ingress link sets
+ private final SetMultimap<DeviceId, LinkKey> srcLinks = createSynchronizedHashMultiMap();
+ private final SetMultimap<DeviceId, LinkKey> dstLinks = createSynchronizedHashMultiMap();
+
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ linkDescs.clear();
+ links.clear();
+ srcLinks.clear();
+ dstLinks.clear();
+ log.info("Stopped");
+ }
+
+ @Override
+ public int getLinkCount() {
+ return links.size();
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ // lock for iteration
+ synchronized (srcLinks) {
+ return FluentIterable.from(srcLinks.get(deviceId))
+ .transform(lookupLink())
+ .filter(notNull())
+ .toSet();
+ }
+ }
+
+ @Override
+ public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ // lock for iteration
+ synchronized (dstLinks) {
+ return FluentIterable.from(dstLinks.get(deviceId))
+ .transform(lookupLink())
+ .filter(notNull())
+ .toSet();
+ }
+ }
+
+ @Override
+ public Link getLink(ConnectPoint src, ConnectPoint dst) {
+ return links.get(linkKey(src, dst));
+ }
+
+ @Override
+ public Set<Link> getEgressLinks(ConnectPoint src) {
+ Set<Link> egress = new HashSet<>();
+ synchronized (srcLinks) {
+ for (LinkKey linkKey : srcLinks.get(src.deviceId())) {
+ if (linkKey.src().equals(src)) {
+ egress.add(links.get(linkKey));
+ }
+ }
+ }
+ return egress;
+ }
+
+ @Override
+ public Set<Link> getIngressLinks(ConnectPoint dst) {
+ Set<Link> ingress = new HashSet<>();
+ synchronized (dstLinks) {
+ for (LinkKey linkKey : dstLinks.get(dst.deviceId())) {
+ if (linkKey.dst().equals(dst)) {
+ ingress.add(links.get(linkKey));
+ }
+ }
+ }
+ return ingress;
+ }
+
+ @Override
+ public LinkEvent createOrUpdateLink(ProviderId providerId,
+ LinkDescription linkDescription) {
+ LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
+
+ Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key);
+ synchronized (descs) {
+ final Link oldLink = links.get(key);
+ // update description
+ createOrUpdateLinkDescription(descs, providerId, linkDescription);
+ final Link newLink = composeLink(descs);
+ if (oldLink == null) {
+ return createLink(key, newLink);
+ }
+ return updateLink(key, oldLink, newLink);
+ }
+ }
+
+ @Override
+ public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
+ Link link = getLink(src, dst);
+ if (link == null) {
+ return null;
+ }
+
+ if (link.isDurable()) {
+ return link.state() == INACTIVE ? null :
+ updateLink(linkKey(link.src(), link.dst()), link,
+ new DefaultLink(link.providerId(),
+ link.src(), link.dst(),
+ link.type(), INACTIVE,
+ link.isDurable(),
+ link.annotations()));
+ }
+ return removeLink(src, dst);
+ }
+
+ // Guarded by linkDescs value (=locking each Link)
+ private LinkDescription createOrUpdateLinkDescription(
+ Map<ProviderId, LinkDescription> descs,
+ ProviderId providerId,
+ LinkDescription linkDescription) {
+
+ // merge existing attributes and merge
+ LinkDescription oldDesc = descs.get(providerId);
+ LinkDescription newDesc = linkDescription;
+ if (oldDesc != null) {
+ // we only allow transition from INDIRECT -> DIRECT
+ final Type newType;
+ if (oldDesc.type() == DIRECT) {
+ newType = DIRECT;
+ } else {
+ newType = linkDescription.type();
+ }
+ SparseAnnotations merged = union(oldDesc.annotations(),
+ linkDescription.annotations());
+ newDesc = new DefaultLinkDescription(linkDescription.src(),
+ linkDescription.dst(),
+ newType, merged);
+ }
+ return descs.put(providerId, newDesc);
+ }
+
+ // Creates and stores the link and returns the appropriate event.
+ // Guarded by linkDescs value (=locking each Link)
+ private LinkEvent createLink(LinkKey key, Link newLink) {
+ links.put(key, newLink);
+ srcLinks.put(newLink.src().deviceId(), key);
+ dstLinks.put(newLink.dst().deviceId(), key);
+ return new LinkEvent(LINK_ADDED, newLink);
+ }
+
+ // Updates, if necessary the specified link and returns the appropriate event.
+ // Guarded by linkDescs value (=locking each Link)
+ private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
+ if (oldLink.state() != newLink.state() ||
+ (oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+ !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
+
+ links.put(key, newLink);
+ // strictly speaking following can be ommitted
+ srcLinks.put(oldLink.src().deviceId(), key);
+ dstLinks.put(oldLink.dst().deviceId(), key);
+ return new LinkEvent(LINK_UPDATED, newLink);
+ }
+ return null;
+ }
+
+ @Override
+ public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
+ final LinkKey key = linkKey(src, dst);
+ Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key);
+ synchronized (descs) {
+ Link link = links.remove(key);
+ descs.clear();
+ if (link != null) {
+ srcLinks.remove(link.src().deviceId(), key);
+ dstLinks.remove(link.dst().deviceId(), key);
+ return new LinkEvent(LINK_REMOVED, link);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Creates concurrent readable, synchronized HashMultimap.
+ *
+ * @return SetMultimap
+ */
+ private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
+ return synchronizedSetMultimap(
+ Multimaps.newSetMultimap(new ConcurrentHashMap<K, Collection<V>>(),
+ () -> Sets.newConcurrentHashSet()));
+ }
+
+ /**
+ * @return primary ProviderID, or randomly chosen one if none exists
+ */
+ // Guarded by linkDescs value (=locking each Link)
+ private ProviderId getBaseProviderId(Map<ProviderId, LinkDescription> providerDescs) {
+
+ ProviderId fallBackPrimary = null;
+ for (Entry<ProviderId, LinkDescription> e : providerDescs.entrySet()) {
+ if (!e.getKey().isAncillary()) {
+ return e.getKey();
+ } else if (fallBackPrimary == null) {
+ // pick randomly as a fallback in case there is no primary
+ fallBackPrimary = e.getKey();
+ }
+ }
+ return fallBackPrimary;
+ }
+
+ // Guarded by linkDescs value (=locking each Link)
+ private Link composeLink(Map<ProviderId, LinkDescription> descs) {
+ ProviderId primary = getBaseProviderId(descs);
+ LinkDescription base = descs.get(verifyNotNull(primary));
+
+ ConnectPoint src = base.src();
+ ConnectPoint dst = base.dst();
+ Type type = base.type();
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ annotations = merge(annotations, base.annotations());
+
+ for (Entry<ProviderId, LinkDescription> e : descs.entrySet()) {
+ if (primary.equals(e.getKey())) {
+ continue;
+ }
+
+ // TODO: should keep track of Description timestamp
+ // and only merge conflicting keys when timestamp is newer
+ // Currently assuming there will never be a key conflict between
+ // providers
+
+ // annotation merging. not so efficient, should revisit later
+ annotations = merge(annotations, e.getValue().annotations());
+ }
+
+ boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
+ return new DefaultLink(primary, src, dst, type, ACTIVE, isDurable, annotations);
+ }
+
+ private Map<ProviderId, LinkDescription> getOrCreateLinkDescriptions(LinkKey key) {
+ Map<ProviderId, LinkDescription> r;
+ r = linkDescs.get(key);
+ if (r != null) {
+ return r;
+ }
+ r = new HashMap<>();
+ final Map<ProviderId, LinkDescription> concurrentlyAdded;
+ concurrentlyAdded = linkDescs.putIfAbsent(key, r);
+ if (concurrentlyAdded == null) {
+ return r;
+ } else {
+ return concurrentlyAdded;
+ }
+ }
+
+ private final Function<LinkKey, Link> lookupLink = new LookupLink();
+
+ private Function<LinkKey, Link> lookupLink() {
+ return lookupLink;
+ }
+
+ private final class LookupLink implements Function<LinkKey, Link> {
+ @Override
+ public Link apply(LinkKey input) {
+ if (input == null) {
+ return null;
+ } else {
+ return links.get(input);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java
new file mode 100644
index 00000000..2d2b2759
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java
@@ -0,0 +1,542 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.collect.Iterables;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+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.Link.Type;
+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.LinkEvent;
+import org.onosproject.net.link.LinkStore;
+import org.onosproject.net.link.LinkStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.*;
+import static org.onosproject.net.link.LinkEvent.Type.*;
+import static org.onosproject.net.NetTestTools.assertAnnotationsEquals;
+
+/**
+ * Test of the simple LinkStore implementation.
+ */
+public class SimpleLinkStoreTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+
+ 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 SparseAnnotations A1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .build();
+ private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+ .remove("A1")
+ .set("B3", "b3")
+ .build();
+ private static final SparseAnnotations A2 = DefaultAnnotations.builder()
+ .set("A2", "a2")
+ .set("B2", "b2")
+ .build();
+ private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
+ .remove("A2")
+ .set("B4", "b4")
+ .build();
+
+ private static final SparseAnnotations DA1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .set(AnnotationKeys.DURABLE, "true")
+ .build();
+ private static final SparseAnnotations DA2 = DefaultAnnotations.builder()
+ .set("A2", "a2")
+ .set("B2", "b2")
+ .set(AnnotationKeys.DURABLE, "true")
+ .build();
+ private static final SparseAnnotations NDA1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .remove(AnnotationKeys.DURABLE)
+ .build();
+
+
+
+ private SimpleLinkStore simpleLinkStore;
+ private LinkStore linkStore;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ simpleLinkStore = new SimpleLinkStore();
+ simpleLinkStore.activate();
+ linkStore = simpleLinkStore;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ simpleLinkStore.deactivate();
+ }
+
+ private void putLink(DeviceId srcId, PortNumber srcNum,
+ DeviceId dstId, PortNumber dstNum,
+ Type type, boolean isDurable,
+ SparseAnnotations... annotations) {
+ ConnectPoint src = new ConnectPoint(srcId, srcNum);
+ ConnectPoint dst = new ConnectPoint(dstId, dstNum);
+ linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type,
+ annotations));
+ }
+
+ private void putLink(LinkKey key, Type type, SparseAnnotations... annotations) {
+ putLink(key.src().deviceId(), key.src().port(),
+ key.dst().deviceId(), key.dst().port(),
+ type, false, annotations);
+ }
+
+ private static void assertLink(DeviceId srcId, PortNumber srcNum,
+ DeviceId dstId, PortNumber dstNum, Type type,
+ Link link) {
+ assertEquals(srcId, link.src().deviceId());
+ assertEquals(srcNum, link.src().port());
+ assertEquals(dstId, link.dst().deviceId());
+ assertEquals(dstNum, link.dst().port());
+ assertEquals(type, link.type());
+ }
+
+ private static void assertLink(LinkKey key, Type type, Link link) {
+ assertLink(key.src().deviceId(), key.src().port(),
+ key.dst().deviceId(), key.dst().port(),
+ type, link);
+ }
+
+ @Test
+ public final void testGetLinkCount() {
+ assertEquals("initialy empty", 0, linkStore.getLinkCount());
+
+ putLink(DID1, P1, DID2, P2, DIRECT, false);
+ putLink(DID2, P2, DID1, P1, DIRECT, false);
+ putLink(DID1, P1, DID2, P2, DIRECT, false);
+
+ assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
+ }
+
+ @Test
+ public final void testGetLinks() {
+ assertEquals("initialy empty", 0,
+ Iterables.size(linkStore.getLinks()));
+
+ LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
+ LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId1, DIRECT);
+
+ assertEquals("expecting 2 unique link", 2,
+ Iterables.size(linkStore.getLinks()));
+
+ Map<LinkKey, Link> links = new HashMap<>();
+ for (Link link : linkStore.getLinks()) {
+ links.put(LinkKey.linkKey(link), link);
+ }
+
+ assertLink(linkId1, DIRECT, links.get(linkId1));
+ assertLink(linkId2, DIRECT, links.get(linkId2));
+ }
+
+ @Test
+ public final void testGetDeviceEgressLinks() {
+ LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
+ LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getDeviceEgressLinks(DID1);
+ assertEquals(2, links1.size());
+ // check
+
+ Set<Link> links2 = linkStore.getDeviceEgressLinks(DID2);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testGetDeviceIngressLinks() {
+ LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
+ LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getDeviceIngressLinks(DID2);
+ assertEquals(2, links1.size());
+ // check
+
+ Set<Link> links2 = linkStore.getDeviceIngressLinks(DID1);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testGetLink() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(src, dst);
+
+ putLink(linkId1, DIRECT);
+
+ Link link = linkStore.getLink(src, dst);
+ assertLink(linkId1, DIRECT, link);
+
+ assertNull("There shouldn't be reverese link",
+ linkStore.getLink(dst, src));
+ }
+
+ @Test
+ public final void testGetEgressLinks() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getEgressLinks(d1P1);
+ assertEquals(1, links1.size());
+ assertLink(linkId1, DIRECT, links1.iterator().next());
+
+ Set<Link> links2 = linkStore.getEgressLinks(d2P2);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testGetIngressLinks() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getIngressLinks(d2P2);
+ assertEquals(1, links1.size());
+ assertLink(linkId1, DIRECT, links1.iterator().next());
+
+ Set<Link> links2 = linkStore.getIngressLinks(d1P1);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testCreateOrUpdateLink() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+ // add link
+ LinkEvent event = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, INDIRECT));
+
+ assertLink(DID1, P1, DID2, P2, INDIRECT, event.subject());
+ assertEquals(LINK_ADDED, event.type());
+
+ // update link type
+ LinkEvent event2 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT));
+
+ assertLink(DID1, P1, DID2, P2, DIRECT, event2.subject());
+ assertEquals(LINK_UPDATED, event2.type());
+
+ // no change
+ LinkEvent event3 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT));
+
+ assertNull("No change event expected", event3);
+ }
+
+ @Test
+ public final void testCreateOrUpdateLinkAncillary() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+ // add Ancillary link
+ LinkEvent event = linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, INDIRECT, A1));
+
+ assertNotNull("Ancillary only link is ignored", event);
+
+ // add Primary link
+ LinkEvent event2 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, INDIRECT, A2));
+
+ assertLink(DID1, P1, DID2, P2, INDIRECT, event2.subject());
+ assertAnnotationsEquals(event2.subject().annotations(), A2, A1);
+ assertEquals(LINK_UPDATED, event2.type());
+
+ // update link type
+ LinkEvent event3 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT, A2));
+ assertLink(DID1, P1, DID2, P2, DIRECT, event3.subject());
+ assertAnnotationsEquals(event3.subject().annotations(), A2, A1);
+ assertEquals(LINK_UPDATED, event3.type());
+
+
+ // no change
+ LinkEvent event4 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT));
+ assertNull("No change event expected", event4);
+
+ // update link annotation (Primary)
+ LinkEvent event5 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT, A2_2));
+ assertLink(DID1, P1, DID2, P2, DIRECT, event5.subject());
+ assertAnnotationsEquals(event5.subject().annotations(), A2, A2_2, A1);
+ assertEquals(LINK_UPDATED, event5.type());
+
+ // update link annotation (Ancillary)
+ LinkEvent event6 = linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, DIRECT, A1_2));
+ assertLink(DID1, P1, DID2, P2, DIRECT, event6.subject());
+ assertAnnotationsEquals(event6.subject().annotations(), A2, A2_2, A1, A1_2);
+ assertEquals(LINK_UPDATED, event6.type());
+
+ // update link type (Ancillary) : ignored
+ LinkEvent event7 = linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, EDGE));
+ assertNull("Ancillary change other than annotation is ignored", event7);
+ }
+
+
+ @Test
+ public final void testRemoveOrDownLink() {
+ removeOrDownLink(false);
+ }
+
+ @Test
+ public final void testRemoveOrDownLinkDurable() {
+ removeOrDownLink(true);
+ }
+
+ private void removeOrDownLink(boolean isDurable) {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+
+ putLink(linkId1, DIRECT, isDurable ? DA1 : A1);
+ putLink(linkId2, DIRECT, isDurable ? DA2 : A2);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ LinkEvent event = linkStore.removeOrDownLink(d1P1, d2P2);
+ assertEquals(isDurable ? LINK_UPDATED : LINK_REMOVED, event.type());
+ assertAnnotationsEquals(event.subject().annotations(), isDurable ? DA1 : A1);
+ LinkEvent event2 = linkStore.removeOrDownLink(d1P1, d2P2);
+ assertNull(event2);
+
+ assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
+ assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(),
+ isDurable ? DA2 : A2);
+
+ // annotations, etc. should not survive remove
+ if (!isDurable) {
+ putLink(linkId1, DIRECT);
+ assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2));
+ assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations());
+ }
+ }
+
+ @Test
+ public final void testRemoveLink() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+
+ putLink(linkId1, DIRECT, A1);
+ putLink(linkId2, DIRECT, A2);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ LinkEvent event = linkStore.removeLink(d1P1, d2P2);
+ assertEquals(LINK_REMOVED, event.type());
+ assertAnnotationsEquals(event.subject().annotations(), A1);
+ LinkEvent event2 = linkStore.removeLink(d1P1, d2P2);
+ assertNull(event2);
+
+ assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
+ assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(), A2);
+
+ // annotations, etc. should not survive remove
+ putLink(linkId1, DIRECT);
+ assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2));
+ assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations());
+ }
+
+ @Test
+ public final void testAncillaryVisible() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+ // add Ancillary link
+ linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, INDIRECT, A1));
+
+ // Ancillary only link should not be visible
+ assertEquals(1, linkStore.getLinkCount());
+ assertNotNull(linkStore.getLink(src, dst));
+ }
+
+ @Test
+ public void testDurableToNonDurable() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+
+ putLink(linkId1, DIRECT, DA1);
+ assertTrue("should be be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+ putLink(linkId1, DIRECT, NDA1);
+ assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+ }
+
+ @Test
+ public void testNonDurableToDurable() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+
+ putLink(linkId1, DIRECT, A1);
+ assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+ putLink(linkId1, DIRECT, DA1);
+ assertTrue("should be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+ }
+
+ // If Delegates should be called only on remote events,
+ // then Simple* should never call them, thus not test required.
+ @Ignore("Ignore until Delegate spec. is clear.")
+ @Test
+ public final void testEvents() throws InterruptedException {
+
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ final LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+
+ final CountDownLatch addLatch = new CountDownLatch(1);
+ LinkStoreDelegate checkAdd = new LinkStoreDelegate() {
+ @Override
+ public void notify(LinkEvent event) {
+ assertEquals(LINK_ADDED, event.type());
+ assertLink(linkId1, INDIRECT, event.subject());
+ addLatch.countDown();
+ }
+ };
+ final CountDownLatch updateLatch = new CountDownLatch(1);
+ LinkStoreDelegate checkUpdate = new LinkStoreDelegate() {
+ @Override
+ public void notify(LinkEvent event) {
+ assertEquals(LINK_UPDATED, event.type());
+ assertLink(linkId1, DIRECT, event.subject());
+ updateLatch.countDown();
+ }
+ };
+ final CountDownLatch removeLatch = new CountDownLatch(1);
+ LinkStoreDelegate checkRemove = new LinkStoreDelegate() {
+ @Override
+ public void notify(LinkEvent event) {
+ assertEquals(LINK_REMOVED, event.type());
+ assertLink(linkId1, DIRECT, event.subject());
+ removeLatch.countDown();
+ }
+ };
+
+ linkStore.setDelegate(checkAdd);
+ putLink(linkId1, INDIRECT);
+ assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+
+ linkStore.unsetDelegate(checkAdd);
+ linkStore.setDelegate(checkUpdate);
+ putLink(linkId1, DIRECT);
+ assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
+
+ linkStore.unsetDelegate(checkUpdate);
+ linkStore.setDelegate(checkRemove);
+ linkStore.removeOrDownLink(d1P1, d2P2);
+ assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
new file mode 100644
index 00000000..ef92ded2
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
@@ -0,0 +1,388 @@
+/*
+ * 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.store.trivial;
+
+import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.ControllerNode.State;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipStore;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Manages inventory of controller mastership over devices using
+ * trivial, non-distributed in-memory structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleMastershipStore
+ extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
+ implements MastershipStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final int NOTHING = 0;
+ private static final int INIT = 1;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ //devices mapped to their masters, to emulate multiple nodes
+ protected final Map<DeviceId, NodeId> masterMap = new HashMap<>();
+ //emulate backups with pile of nodes
+ protected final Map<DeviceId, List<NodeId>> backups = new HashMap<>();
+ //terms
+ protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>();
+
+ @Activate
+ public void activate() {
+ if (clusterService == null) {
+ // just for ease of unit test
+ final ControllerNode instance =
+ new DefaultControllerNode(new NodeId("local"),
+ IpAddress.valueOf("127.0.0.1"));
+
+ clusterService = new ClusterService() {
+
+ private final DateTime creationTime = DateTime.now();
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return instance;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return ImmutableSet.of(instance);
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ if (instance.id().equals(nodeId)) {
+ return instance;
+ }
+ return null;
+ }
+
+ @Override
+ public State getState(NodeId nodeId) {
+ if (instance.id().equals(nodeId)) {
+ return State.ACTIVE;
+ } else {
+ return State.INACTIVE;
+ }
+ }
+
+ @Override
+ public DateTime getLastUpdated(NodeId nodeId) {
+ return creationTime;
+ }
+
+ @Override
+ public void addListener(ClusterEventListener listener) {
+ }
+
+ @Override
+ public void removeListener(ClusterEventListener listener) {
+ }
+ };
+ }
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipEvent> setMaster(NodeId nodeId, DeviceId deviceId) {
+
+ MastershipRole role = getRole(nodeId, deviceId);
+ switch (role) {
+ case MASTER:
+ // no-op
+ return CompletableFuture.completedFuture(null);
+ case STANDBY:
+ case NONE:
+ NodeId prevMaster = masterMap.put(deviceId, nodeId);
+ incrementTerm(deviceId);
+ removeFromBackups(deviceId, nodeId);
+ addToBackup(deviceId, prevMaster);
+ break;
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ return null;
+ }
+
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
+ }
+
+ @Override
+ public NodeId getMaster(DeviceId deviceId) {
+ return masterMap.get(deviceId);
+ }
+
+ // synchronized for atomic read
+ @Override
+ public synchronized RoleInfo getNodes(DeviceId deviceId) {
+ return new RoleInfo(masterMap.get(deviceId),
+ backups.getOrDefault(deviceId, ImmutableList.of()));
+ }
+
+ @Override
+ public Set<DeviceId> getDevices(NodeId nodeId) {
+ Set<DeviceId> ids = new HashSet<>();
+ for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
+ if (Objects.equals(d.getValue(), nodeId)) {
+ ids.add(d.getKey());
+ }
+ }
+ return ids;
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipRole> requestRole(DeviceId deviceId) {
+ //query+possible reelection
+ NodeId node = clusterService.getLocalNode().id();
+ MastershipRole role = getRole(node, deviceId);
+
+ switch (role) {
+ case MASTER:
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ case STANDBY:
+ if (getMaster(deviceId) == null) {
+ // no master => become master
+ masterMap.put(deviceId, node);
+ incrementTerm(deviceId);
+ // remove from backup list
+ removeFromBackups(deviceId, node);
+ notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId,
+ getNodes(deviceId)));
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ }
+ return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+ case NONE:
+ if (getMaster(deviceId) == null) {
+ // no master => become master
+ masterMap.put(deviceId, node);
+ incrementTerm(deviceId);
+ notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId,
+ getNodes(deviceId)));
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ }
+ // add to backup list
+ if (addToBackup(deviceId, node)) {
+ notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId,
+ getNodes(deviceId)));
+ }
+ return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ }
+ return CompletableFuture.completedFuture(role);
+ }
+
+ // add to backup if not there already, silently ignores null node
+ private synchronized boolean addToBackup(DeviceId deviceId, NodeId nodeId) {
+ boolean modified = false;
+ List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+ if (nodeId != null && !stbys.contains(nodeId)) {
+ stbys.add(nodeId);
+ modified = true;
+ }
+ backups.put(deviceId, stbys);
+ return modified;
+ }
+
+ private synchronized boolean removeFromBackups(DeviceId deviceId, NodeId node) {
+ List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+ boolean modified = stbys.remove(node);
+ backups.put(deviceId, stbys);
+ return modified;
+ }
+
+ private synchronized void incrementTerm(DeviceId deviceId) {
+ AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
+ term.incrementAndGet();
+ termMap.put(deviceId, term);
+ }
+
+ @Override
+ public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
+ //just query
+ NodeId current = masterMap.get(deviceId);
+ MastershipRole role;
+
+ if (current != null && current.equals(nodeId)) {
+ return MastershipRole.MASTER;
+ }
+
+ if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
+ role = MastershipRole.STANDBY;
+ } else {
+ role = MastershipRole.NONE;
+ }
+ return role;
+ }
+
+ // synchronized for atomic read
+ @Override
+ public synchronized MastershipTerm getTermFor(DeviceId deviceId) {
+ if ((termMap.get(deviceId) == null)) {
+ return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
+ }
+ return MastershipTerm.of(
+ masterMap.get(deviceId), termMap.get(deviceId).get());
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipEvent> setStandby(NodeId nodeId, DeviceId deviceId) {
+ MastershipRole role = getRole(nodeId, deviceId);
+ switch (role) {
+ case MASTER:
+ NodeId backup = reelect(deviceId, nodeId);
+ if (backup == null) {
+ // no master alternative
+ masterMap.remove(deviceId);
+ // TODO: Should there be new event type for no MASTER?
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
+ } else {
+ NodeId prevMaster = masterMap.put(deviceId, backup);
+ incrementTerm(deviceId);
+ addToBackup(deviceId, prevMaster);
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
+ }
+
+ case STANDBY:
+ case NONE:
+ boolean modified = addToBackup(deviceId, nodeId);
+ if (modified) {
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId)));
+ }
+ break;
+
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ }
+ return null;
+ }
+
+ //dumbly selects next-available node that's not the current one
+ //emulate leader election
+ private synchronized NodeId reelect(DeviceId did, NodeId nodeId) {
+ List<NodeId> stbys = backups.getOrDefault(did, Collections.emptyList());
+ NodeId backup = null;
+ for (NodeId n : stbys) {
+ if (!n.equals(nodeId)) {
+ backup = n;
+ break;
+ }
+ }
+ stbys.remove(backup);
+ return backup;
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipEvent> relinquishRole(NodeId nodeId, DeviceId deviceId) {
+ MastershipRole role = getRole(nodeId, deviceId);
+ switch (role) {
+ case MASTER:
+ NodeId backup = reelect(deviceId, nodeId);
+ masterMap.put(deviceId, backup);
+ incrementTerm(deviceId);
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
+
+ case STANDBY:
+ if (removeFromBackups(deviceId, nodeId)) {
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId)));
+ }
+ break;
+
+ case NONE:
+ break;
+
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public synchronized void relinquishAllRole(NodeId nodeId) {
+ List<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
+ Set<DeviceId> toRelinquish = new HashSet<>();
+
+ masterMap.entrySet().stream()
+ .filter(entry -> nodeId.equals(entry.getValue()))
+ .forEach(entry -> toRelinquish.add(entry.getKey()));
+
+ backups.entrySet().stream()
+ .filter(entry -> entry.getValue().contains(nodeId))
+ .forEach(entry -> toRelinquish.add(entry.getKey()));
+
+ toRelinquish.forEach(deviceId -> {
+ eventFutures.add(relinquishRole(nodeId, deviceId));
+ });
+
+ eventFutures.forEach(future -> {
+ future.whenComplete((event, error) -> {
+ notifyDelegate(event);
+ });
+ });
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java
new file mode 100644
index 00000000..672fc503
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.store.trivial;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+
+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.junit.Assert.assertTrue;
+import static org.onosproject.mastership.MastershipEvent.Type.*;
+import static org.onosproject.net.MastershipRole.*;
+
+/**
+ * Test for the simple MastershipStore implementation.
+ */
+public class SimpleMastershipStoreTest {
+
+ private static final DeviceId DID1 = DeviceId.deviceId("of:01");
+ private static final DeviceId DID2 = DeviceId.deviceId("of:02");
+ private static final DeviceId DID3 = DeviceId.deviceId("of:03");
+ private static final DeviceId DID4 = DeviceId.deviceId("of:04");
+
+ private static final NodeId N1 = new NodeId("local");
+ private static final NodeId N2 = new NodeId("other");
+
+ private SimpleMastershipStore sms;
+
+ @Before
+ public void setUp() throws Exception {
+ sms = new SimpleMastershipStore();
+ sms.activate();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ sms.deactivate();
+ }
+
+ @Test
+ public void getRole() {
+ //special case, no backup or master
+ put(DID1, N1, false, false);
+ assertEquals("wrong role", NONE, sms.getRole(N1, DID1));
+
+ //backup exists but we aren't mapped
+ put(DID2, N1, false, true);
+ assertEquals("wrong role", STANDBY, sms.getRole(N1, DID2));
+
+ //N2 is master
+ put(DID3, N2, true, true);
+ assertEquals("wrong role", MASTER, sms.getRole(N2, DID3));
+
+ //N2 is master but N1 is only in backups set
+ put(DID4, N1, false, true);
+ put(DID4, N2, true, false);
+ assertEquals("wrong role", STANDBY, sms.getRole(N1, DID4));
+ }
+
+ @Test
+ public void getMaster() {
+ put(DID3, N2, true, true);
+ assertEquals("wrong role", MASTER, sms.getRole(N2, DID3));
+ assertEquals("wrong device", N2, sms.getMaster(DID3));
+ }
+
+ @Test
+ public void setMaster() {
+ put(DID1, N1, false, false);
+ assertEquals("wrong event", MASTER_CHANGED, Futures.getUnchecked(sms.setMaster(N1, DID1)).type());
+ assertEquals("wrong role", MASTER, sms.getRole(N1, DID1));
+ //set node that's already master - should be ignored
+ assertNull("wrong event", Futures.getUnchecked(sms.setMaster(N1, DID1)));
+
+ //set STANDBY to MASTER
+ put(DID2, N1, false, true);
+ assertEquals("wrong role", STANDBY, sms.getRole(N1, DID2));
+ assertEquals("wrong event", MASTER_CHANGED, Futures.getUnchecked(sms.setMaster(N1, DID2)).type());
+ assertEquals("wrong role", MASTER, sms.getRole(N1, DID2));
+ }
+
+ @Test
+ public void getDevices() {
+ Set<DeviceId> d = Sets.newHashSet(DID1, DID2);
+
+ put(DID1, N2, true, true);
+ put(DID2, N2, true, true);
+ put(DID3, N1, true, true);
+ assertTrue("wrong devices", d.equals(sms.getDevices(N2)));
+ }
+
+ @Test
+ public void getTermFor() {
+ put(DID1, N1, true, true);
+ assertEquals("wrong term", MastershipTerm.of(N1, 0), sms.getTermFor(DID1));
+
+ //switch to N2 and back - 2 term switches
+ sms.setMaster(N2, DID1);
+ sms.setMaster(N1, DID1);
+ assertEquals("wrong term", MastershipTerm.of(N1, 2), sms.getTermFor(DID1));
+ }
+
+ @Test
+ public void requestRole() {
+ //NONE - become MASTER
+ put(DID1, N1, false, false);
+ assertEquals("wrong role", MASTER, Futures.getUnchecked(sms.requestRole(DID1)));
+
+ //was STANDBY - become MASTER
+ put(DID2, N1, false, true);
+ assertEquals("wrong role", MASTER, Futures.getUnchecked(sms.requestRole(DID2)));
+
+ //other MASTER - stay STANDBY
+ put(DID3, N2, true, false);
+ assertEquals("wrong role", STANDBY, Futures.getUnchecked(sms.requestRole(DID3)));
+
+ //local (N1) is MASTER - stay MASTER
+ put(DID4, N1, true, true);
+ assertEquals("wrong role", MASTER, Futures.getUnchecked(sms.requestRole(DID4)));
+ }
+
+ @Test
+ public void unsetMaster() {
+ //NONE - record backup but take no other action
+ put(DID1, N1, false, false);
+ sms.setStandby(N1, DID1);
+ assertTrue("not backed up", sms.backups.get(DID1).contains(N1));
+ int prev = sms.termMap.get(DID1).get();
+ sms.setStandby(N1, DID1);
+ assertEquals("term should not change", prev, sms.termMap.get(DID1).get());
+
+ //no backup, MASTER
+ put(DID1, N1, true, false);
+ assertNull("expect no MASTER event", Futures.getUnchecked(sms.setStandby(N1, DID1)).roleInfo().master());
+ assertNull("wrong node", sms.masterMap.get(DID1));
+
+ //backup, switch
+ sms.masterMap.clear();
+ put(DID1, N1, true, true);
+ put(DID1, N2, false, true);
+ put(DID2, N2, true, true);
+ MastershipEvent event = Futures.getUnchecked(sms.setStandby(N1, DID1));
+ assertEquals("wrong event", MASTER_CHANGED, event.type());
+ assertEquals("wrong master", N2, event.roleInfo().master());
+ }
+
+ //helper to populate master/backup structures
+ private void put(DeviceId dev, NodeId node, boolean master, boolean backup) {
+ if (master) {
+ sms.masterMap.put(dev, node);
+ } else if (backup) {
+ List<NodeId> stbys = sms.backups.getOrDefault(dev, new ArrayList<>());
+ stbys.add(node);
+ sms.backups.put(dev, stbys);
+ }
+ sms.termMap.put(dev, new AtomicInteger());
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java
new file mode 100644
index 00000000..4345abaf
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketEvent.Type;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStore;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.onosproject.store.AbstractStore;
+
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Simple single instance implementation of the packet store.
+ */
+@Component(immediate = true)
+@Service
+public class SimplePacketStore
+ extends AbstractStore<PacketEvent, PacketStoreDelegate>
+ implements PacketStore {
+
+ private Set<PacketRequest> requests = Sets.newConcurrentHashSet();
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ notifyDelegate(new PacketEvent(Type.EMIT, packet));
+ }
+
+ @Override
+ public boolean requestPackets(PacketRequest request) {
+ return requests.add(request);
+ }
+
+ @Override
+ public boolean cancelPackets(PacketRequest request) {
+ return requests.remove(request);
+ }
+
+ @Override
+ public Set<PacketRequest> existingRequests() {
+ return Collections.unmodifiableSet(requests);
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java
new file mode 100644
index 00000000..370686f9
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java
@@ -0,0 +1,211 @@
+/*
+ * 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.store.trivial;
+
+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.Service;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.statistic.StatisticStore;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Maintains statistics using RPC calls to collect stats from remote instances
+ * on demand.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleStatisticStore implements StatisticStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private Map<ConnectPoint, InternalStatisticRepresentation>
+ representations = new ConcurrentHashMap<>();
+
+ private Map<ConnectPoint, Set<FlowEntry>> previous = new ConcurrentHashMap<>();
+ private Map<ConnectPoint, Set<FlowEntry>> current = new ConcurrentHashMap<>();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public void prepareForStatistics(FlowRule rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep;
+ synchronized (representations) {
+ rep = getOrCreateRepresentation(cp);
+ }
+ rep.prepare();
+ }
+
+ @Override
+ public synchronized void removeFromStatistics(FlowRule rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep = representations.get(cp);
+ if (rep != null && rep.remove(rule)) {
+ updatePublishedStats(cp, Collections.emptySet());
+ }
+ Set<FlowEntry> values = current.get(cp);
+ if (values != null) {
+ values.remove(rule);
+ }
+ values = previous.get(cp);
+ if (values != null) {
+ values.remove(rule);
+ }
+
+ }
+
+ @Override
+ public void addOrUpdateStatistic(FlowEntry rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep = representations.get(cp);
+ if (rep != null && rep.submit(rule)) {
+ updatePublishedStats(cp, rep.get());
+ }
+ }
+
+ private synchronized void updatePublishedStats(ConnectPoint cp,
+ Set<FlowEntry> flowEntries) {
+ Set<FlowEntry> curr = current.get(cp);
+ if (curr == null) {
+ curr = new HashSet<>();
+ }
+ previous.put(cp, curr);
+ current.put(cp, flowEntries);
+
+ }
+
+ @Override
+ public Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) {
+ return getCurrentStatisticInternal(connectPoint);
+ }
+
+ private synchronized Set<FlowEntry> getCurrentStatisticInternal(ConnectPoint connectPoint) {
+ return current.get(connectPoint);
+ }
+
+ @Override
+ public Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) {
+ return getPreviousStatisticInternal(connectPoint);
+ }
+
+ private synchronized Set<FlowEntry> getPreviousStatisticInternal(ConnectPoint connectPoint) {
+ return previous.get(connectPoint);
+ }
+
+ private InternalStatisticRepresentation getOrCreateRepresentation(ConnectPoint cp) {
+
+ if (representations.containsKey(cp)) {
+ return representations.get(cp);
+ } else {
+ InternalStatisticRepresentation rep = new InternalStatisticRepresentation();
+ representations.put(cp, rep);
+ return rep;
+ }
+
+ }
+
+ private ConnectPoint buildConnectPoint(FlowRule rule) {
+ PortNumber port = getOutput(rule);
+
+ if (port == null) {
+ return null;
+ }
+ ConnectPoint cp = new ConnectPoint(rule.deviceId(), port);
+ return cp;
+ }
+
+ private PortNumber getOutput(FlowRule rule) {
+ for (Instruction i : rule.treatment().immediate()) {
+ if (i.type() == Instruction.Type.OUTPUT) {
+ Instructions.OutputInstruction out = (Instructions.OutputInstruction) i;
+ return out.port();
+ }
+ if (i.type() == Instruction.Type.DROP) {
+ return PortNumber.P0;
+ }
+ }
+ return null;
+ }
+
+ private class InternalStatisticRepresentation {
+
+ private final AtomicInteger counter = new AtomicInteger(0);
+ private final Set<FlowEntry> rules = new HashSet<>();
+
+ public void prepare() {
+ counter.incrementAndGet();
+ }
+
+ public synchronized boolean remove(FlowRule rule) {
+ rules.remove(rule);
+ return counter.decrementAndGet() == 0;
+ }
+
+ public synchronized boolean submit(FlowEntry rule) {
+ if (rules.contains(rule)) {
+ rules.remove(rule);
+ }
+ rules.add(rule);
+ if (counter.get() == 0) {
+ return true;
+ } else {
+ return counter.decrementAndGet() == 0;
+ }
+ }
+
+ public synchronized Set<FlowEntry> get() {
+ counter.set(rules.size());
+ return Sets.newHashSet(rules);
+ }
+
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java
new file mode 100644
index 00000000..6a89c019
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java
@@ -0,0 +1,157 @@
+/*
+ * 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.store.trivial;
+
+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.onosproject.common.DefaultTopology;
+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.ProviderId;
+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.TopologyStore;
+import org.onosproject.net.topology.TopologyStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of topology snapshots using trivial in-memory
+ * structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleTopologyStore
+ extends AbstractStore<TopologyEvent, TopologyStoreDelegate>
+ implements TopologyStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private volatile DefaultTopology current;
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+ @Override
+ public Topology currentTopology() {
+ return current;
+ }
+
+ @Override
+ public boolean isLatest(Topology topology) {
+ // Topology is current only if it is the same as our current topology
+ return topology == current;
+ }
+
+ @Override
+ public TopologyGraph getGraph(Topology topology) {
+ return defaultTopology(topology).getGraph();
+ }
+
+ @Override
+ public Set<TopologyCluster> getClusters(Topology topology) {
+ return defaultTopology(topology).getClusters();
+ }
+
+ @Override
+ public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
+ return defaultTopology(topology).getCluster(clusterId);
+ }
+
+ @Override
+ public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
+ return defaultTopology(topology).getClusterDevices(cluster);
+ }
+
+ @Override
+ public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
+ return defaultTopology(topology).getClusterLinks(cluster);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+ return defaultTopology(topology).getPaths(src, dst);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+ LinkWeight weight) {
+ return defaultTopology(topology).getPaths(src, dst, weight);
+ }
+
+ @Override
+ public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
+ return defaultTopology(topology).isInfrastructure(connectPoint);
+ }
+
+ @Override
+ public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
+ return defaultTopology(topology).isBroadcastPoint(connectPoint);
+ }
+
+ @Override
+ public TopologyEvent updateTopology(ProviderId providerId,
+ GraphDescription graphDescription,
+ List<Event> reasons) {
+ // First off, make sure that what we're given is indeed newer than
+ // what we already have.
+ if (current != null && graphDescription.timestamp() < current.time()) {
+ return null;
+ }
+
+ // Have the default topology construct self from the description data.
+ DefaultTopology newTopology =
+ new DefaultTopology(providerId, graphDescription);
+
+ // Promote the new topology to current and return a ready-to-send event.
+ synchronized (this) {
+ current = newTopology;
+ return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
+ current, reasons);
+ }
+ }
+
+ // Validates the specified topology and returns it as a default
+ private DefaultTopology defaultTopology(Topology topology) {
+ if (topology instanceof DefaultTopology) {
+ return (DefaultTopology) topology;
+ }
+ throw new IllegalArgumentException("Topology class " + topology.getClass() +
+ " not supported");
+ }
+
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java
new file mode 100644
index 00000000..2ee41945
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java
@@ -0,0 +1,83 @@
+/*
+ * 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.store.trivial;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import org.onosproject.store.Timestamp;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A Timestamp that derives its value from the system clock time (in ns)
+ * on the controller where it is generated.
+ */
+public class SystemClockTimestamp implements Timestamp {
+
+ private final long nanoTimestamp;
+
+ public SystemClockTimestamp() {
+ nanoTimestamp = System.nanoTime();
+ }
+
+ public SystemClockTimestamp(long timestamp) {
+ nanoTimestamp = timestamp;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof SystemClockTimestamp,
+ "Must be SystemClockTimestamp", o);
+ SystemClockTimestamp that = (SystemClockTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.nanoTimestamp, that.nanoTimestamp)
+ .result();
+ }
+ @Override
+ public int hashCode() {
+ return Objects.hash(nanoTimestamp);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SystemClockTimestamp)) {
+ return false;
+ }
+ SystemClockTimestamp that = (SystemClockTimestamp) obj;
+ return Objects.equals(this.nanoTimestamp, that.nanoTimestamp);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("nanoTimestamp", nanoTimestamp)
+ .toString();
+ }
+
+ public long nanoTimestamp() {
+ return nanoTimestamp;
+ }
+
+ public long systemTimestamp() {
+ return nanoTimestamp / 1_000_000; // convert ns to ms
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java
new file mode 100644
index 00000000..2b0c36b8
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/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.
+ */
+
+/**
+ * Implementations of in-memory stores suitable for unit testing and
+ * experimentation; not for production use.
+ */
+package org.onosproject.store.trivial;
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json
new file mode 100644
index 00000000..aaa72a72
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json
@@ -0,0 +1,5 @@
+{
+ "type":"AnnotationConstraint",
+ "key":"key",
+ "threshold":123.0
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json
new file mode 100644
index 00000000..340bdbac
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json
@@ -0,0 +1,3 @@
+{
+ "type":"AsymmetricPathConstraint"
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json
new file mode 100644
index 00000000..34750061
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json
@@ -0,0 +1,4 @@
+{
+ "type":"BandwidthConstraint",
+ "bandwidth":345.678
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json
new file mode 100644
index 00000000..fedea251
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json
@@ -0,0 +1,19 @@
+{
+ "type": "HostToHostIntent",
+ "appId": "test",
+ "selector": {"criteria": []},
+ "treatment": {
+ "instructions": [],
+ "deferred": []
+ },
+ "priority": 7,
+ "constraints": [
+ {
+ "inclusive": false,
+ "types": ["OPTICAL"],
+ "type": "LinkTypeConstraint"
+ }
+ ],
+ "one": "00:00:00:00:00:02/-1",
+ "two": "00:00:00:00:00:05/-1"
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json
new file mode 100644
index 00000000..4ac37631
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json
@@ -0,0 +1,4 @@
+{
+ "type":"LambdaConstraint",
+ "lambda":444
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json
new file mode 100644
index 00000000..1c46e5e8
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json
@@ -0,0 +1,4 @@
+{
+ "type":"LatencyConstraint",
+ "latencyMillis":111
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json
new file mode 100644
index 00000000..8b766da0
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json
@@ -0,0 +1,5 @@
+{
+ "inclusive":false,
+ "types":["DIRECT","OPTICAL"],
+ "type":"LinkTypeConstraint"
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json
new file mode 100644
index 00000000..35dcb0fe
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json
@@ -0,0 +1,4 @@
+{
+ "type":"ObstacleConstraint",
+ "obstacles":["of:dev1","of:dev2","of:dev3"]
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json
new file mode 100644
index 00000000..b941bef8
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json
@@ -0,0 +1,38 @@
+{
+ "type": "PointToPointIntent",
+ "appId": "test",
+ "selector": {
+ "criteria": [
+ {
+ "type": "ETH_DST",
+ "mac": "11:22:33:44:55:66"
+ }
+ ]
+ },
+ "treatment": {
+ "instructions": [
+ {
+ "type": "L2MODIFICATION",
+ "subtype": "ETH_SRC",
+ "mac": "22:33:44:55:66:77"
+ }
+ ],
+ "deferred": []
+ },
+ "priority": 55,
+ "constraints": [
+ {
+ "inclusive": false,
+ "types": ["OPTICAL"],
+ "type": "LinkTypeConstraint"
+ }
+ ],
+ "ingressPoint": {
+ "port": "1",
+ "device": "of:0000000000000001"
+ },
+ "egressPoint": {
+ "port": "2",
+ "device": "of:0000000000000007"
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json
new file mode 100644
index 00000000..7009cf98
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json
@@ -0,0 +1,4 @@
+{
+ "type":"WaypointConstraint",
+ "waypoints":["of:devA","of:devB","of:devC"]
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json
new file mode 100644
index 00000000..1a96e92f
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json
@@ -0,0 +1,44 @@
+{
+ "priority":1,
+ "isPermanent":"false",
+ "timeout":1,
+ "deviceId":"of:0000000000000001",
+ "selector":
+ {"criteria":
+ [
+ {"type":"IN_PORT", "port":23},
+ {"type":"IN_PHY_PORT", "port":44},
+ {"type":"METADATA", "metadata":123456},
+ {"type":"ETH_TYPE","ethType":2054},
+ {"type":"ETH_SRC","mac":"00:11:22:33:44:55"},
+ {"type":"ETH_DST","mac":"00:11:22:33:44:55"},
+ {"type":"VLAN_VID","vlanId":777},
+ {"type":"VLAN_PCP","priority":3},
+ {"type":"IP_DSCP","ipDscp":2},
+ {"type":"IP_ECN","ipEcn":1},
+ {"type":"IP_PROTO","protocol":4},
+ {"type":"IPV4_SRC", "ip":"1.2.0.0/32"},
+ {"type":"IPV4_DST", "ip":"2.2.0.0/32"},
+ {"type":"IPV6_SRC", "ip":"3.2.0.0/32"},
+ {"type":"IPV6_DST", "ip":"4.2.0.0/32"},
+ {"type":"TCP_SRC", "tcpPort":80},
+ {"type":"TCP_DST", "tcpPort":443},
+ {"type":"UDP_SRC", "udpPort":180},
+ {"type":"UDP_DST", "udpPort":1443},
+ {"type":"SCTP_SRC", "sctpPort":280},
+ {"type":"SCTP_DST", "sctpPort":2443},
+ {"type":"ICMPV4_TYPE", "icmpType":24},
+ {"type":"ICMPV4_CODE", "icmpCode":16},
+ {"type":"ICMPV6_TYPE", "icmpv6Type":14},
+ {"type":"ICMPV6_CODE", "icmpv6Code":6},
+ {"type":"IPV6_FLABEL", "flowLabel":8},
+ {"type":"IPV6_ND_TARGET", "targetAddress":"1111:2222:3333:4444:5555:6666:7777:8888"},
+ {"type":"IPV6_ND_SLL", "mac":"00:11:22:33:44:56"},
+ {"type":"IPV6_ND_TLL", "mac":"00:11:22:33:44:57"},
+ {"type":"MPLS_LABEL", "label":123},
+ {"type":"IPV6_EXTHDR", "exthdrFlags":99},
+ {"type":"OCH_SIGID", "lambda":122},
+ {"type":"TUNNEL_ID", "tunnelId":100}
+ ]
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
new file mode 100644
index 00000000..74b9546a
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
@@ -0,0 +1,39 @@
+{
+ "priority":1,
+ "isPermanent":"false",
+ "timeout":1,
+ "deviceId":"of:0000000000000001",
+ "treatment":
+ {
+ "instructions":
+ [
+ {"type":"OUTPUT","port":-3},
+ {"type":"DROP"},
+ {"type":"L2MODIFICATION","subtype":"ETH_SRC","mac":"12:34:56:78:90:12"},
+ {"type":"L2MODIFICATION","subtype":"ETH_DST","mac":"98:76:54:32:01:00"},
+ {"type":"L2MODIFICATION","subtype":"VLAN_ID","vlanId":22},
+ {"type":"L2MODIFICATION","subtype":"VLAN_PCP","vlanPcp":1},
+ {"type":"L2MODIFICATION","subtype":"MPLS_LABEL","label":1048575},
+ {"type":"L2MODIFICATION","subtype":"MPLS_PUSH"},
+ {"type":"L2MODIFICATION","subtype":"MPLS_POP"},
+ {"type":"L2MODIFICATION","subtype":"DEC_MPLS_TTL"},
+ {"type":"L2MODIFICATION","subtype":"VLAN_POP"},
+ {"type":"L2MODIFICATION","subtype":"VLAN_PUSH"},
+ {"type":"L2MODIFICATION","subtype":"TUNNEL_ID", "tunnelId":100},
+ {"type":"L3MODIFICATION","subtype":"IPV4_SRC", "ip":"1.2.3.4"},
+ {"type":"L3MODIFICATION","subtype":"IPV4_DST", "ip":"1.2.3.3"},
+ {"type":"L3MODIFICATION","subtype":"IPV6_SRC", "ip":"1.2.3.2"},
+ {"type":"L3MODIFICATION","subtype":"IPV6_DST", "ip":"1.2.3.1"},
+ {"type":"L3MODIFICATION","subtype":"IPV6_FLABEL", "flowLabel":8},
+ {"type":"L0MODIFICATION","subtype":"LAMBDA","lambda":7},
+ {"type":"L0MODIFICATION","subtype":"OCH","gridType":"DWDM",
+ "channelSpacing":"CHL_100GHZ","spacingMultiplier":4,"slotGranularity":8},
+ {"type":"L4MODIFICATION","subtype":"TCP_DST","tcpPort":40001},
+ {"type":"L4MODIFICATION","subtype":"TCP_SRC","tcpPort":40002},
+ {"type":"L4MODIFICATION","subtype":"UDP_DST","udpPort":40003},
+ {"type":"L4MODIFICATION","subtype":"UDP_SRC","udpPort":40004}
+ ],
+ "deferred":[]
+ },
+ "selector": {"criteria":[{"type":"ETH_TYPE","ethType":2054}]}
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json
new file mode 100644
index 00000000..49d6b1ce
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json
@@ -0,0 +1,20 @@
+{
+ "priority":1,
+ "isPermanent":"false",
+ "timeout":1,
+ "deviceId":"of:0000000000000001",
+ "selector":
+ {"criteria":
+ [
+ {"type":"OCH_SIGID",
+ "ochSignalId":
+ {
+ "gridType":"CWDM",
+ "channelSpacing":"CHL_25GHZ",
+ "spacingMultiplier":3,
+ "slotGranularity":4
+ }
+ }
+ ]
+ }
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json
new file mode 100644
index 00000000..dc241f55
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json
@@ -0,0 +1,12 @@
+{
+ "priority":1,
+ "isPermanent":"false",
+ "timeout":1,
+ "deviceId":"of:0000000000000001",
+ "treatment":
+ {"instructions":
+ [{"type":"OUTPUT","port":-3}],"deferred":[]},
+ "selector":
+ {"criteria":
+ [{"type":"ETH_TYPE","ethType":2054}]}
+}
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml
new file mode 100644
index 00000000..0d91a735
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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.
+ -->
+<app name="org.foo.app" origin="Circus" version="1.2.a"
+ featuresRepo="mvn:org.foo-features/1.2a/xml/features"
+ features="foo,bar">
+ <description>Awesome application from Circus, Inc.</description>
+ <security>
+ <role>ADMIN</role>
+ <permissions>
+ <app-perm>FLOWRULE_WRITE</app-perm>
+ <app-perm>FLOWRULE_READ</app-perm>
+ </permissions>
+
+ </security>
+
+</app>
diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip
new file mode 100644
index 00000000..436cd755
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip
Binary files differ
diff --git a/framework/src/onos/core/net/pom.xml b/framework/src/onos/core/net/pom.xml
new file mode 100644
index 00000000..4ba04c50
--- /dev/null
+++ b/framework/src/onos/core/net/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core-net</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS network control core subsystems</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-dist</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-incubator-api</artifactId>
+ <scope>test</scope>
+ <classifier>tests</classifier>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-incubator-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>org.apache.karaf.features.core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.system</groupId>
+ <artifactId>org.apache.karaf.system.core</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
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);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/pom.xml b/framework/src/onos/core/pom.xml
new file mode 100644
index 00000000..0f7dd8c8
--- /dev/null
+++ b/framework/src/onos/core/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core</artifactId>
+ <packaging>pom</packaging>
+
+ <description>ONOS Core root project</description>
+
+ <modules>
+ <module>api</module>
+ <module>common</module>
+ <module>net</module>
+ <module>store</module>
+ <module>security</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-maven-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/framework/src/onos/core/security/pom.xml b/framework/src/onos/core/security/pom.xml
new file mode 100644
index 00000000..67e74a1e
--- /dev/null
+++ b/framework/src/onos/core/security/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>onos-core</artifactId>
+ <groupId>org.onosproject</groupId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-security</artifactId>
+ <packaging>bundle</packaging>
+
+
+ <description>Security-Mode ONOS project</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>org.apache.karaf.features.core</artifactId>
+ </dependency>
+ <!-- Adding dependency to pull the artifact from Maven Central.
+ Note: This is just needed for the onos-security feature. -->
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>org.apache.felix.framework.security</artifactId>
+ <version>2.2.0.onos</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project> \ No newline at end of file
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/DefaultPolicyBuilder.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/DefaultPolicyBuilder.java
new file mode 100644
index 00000000..8ae1a1ae
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/DefaultPolicyBuilder.java
@@ -0,0 +1,433 @@
+package org.onosproject.security.impl;
+
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onosproject.security.AppPermission;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.core.CoreService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.driver.DriverAdminService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostAdminService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentClockService;
+import org.onosproject.net.intent.PartitionService;
+import org.onosproject.net.link.LinkAdminService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.proxyarp.ProxyArpService;
+import org.onosproject.net.resource.link.LinkResourceService;
+import org.onosproject.net.statistic.StatisticService;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.security.SecurityAdminService;
+import org.onosproject.store.service.StorageAdminService;
+import org.onosproject.store.service.StorageService;
+import org.osgi.framework.BundlePermission;
+import org.osgi.framework.CapabilityPermission;
+import org.osgi.framework.ServicePermission;
+import org.osgi.framework.PackagePermission;
+import org.osgi.framework.AdaptPermission;
+import org.osgi.service.cm.ConfigurationPermission;
+
+import javax.net.ssl.SSLPermission;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.PrivateCredentialPermission;
+import javax.security.auth.kerberos.DelegationPermission;
+import javax.sound.sampled.AudioPermission;
+import java.io.FilePermission;
+import java.io.SerializablePermission;
+import java.net.NetPermission;
+import java.net.SocketPermission;
+import java.security.Permissions;
+import java.sql.SQLPermission;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.PropertyPermission;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.security.Permission;
+import java.util.logging.LoggingPermission;
+
+import static org.onosproject.security.AppPermission.Type.*;
+
+public final class DefaultPolicyBuilder {
+
+ protected static ConcurrentHashMap<AppPermission.Type,
+ Set<String>> serviceDirectory = getServiceDirectory();
+
+ protected static List<Permission> defaultPermissions = getDefaultPerms();
+ protected static List<Permission> adminServicePermissions = getAdminDefaultPerms();
+
+ private DefaultPolicyBuilder(){
+ }
+
+ public static List<Permission> getUserApplicationPermissions(Set<org.onosproject.security.Permission> permissions) {
+ List<Permission> perms = Lists.newArrayList();
+ perms.addAll(defaultPermissions);
+ perms.addAll(convertToJavaPermissions(permissions));
+ return optimizePermissions(perms);
+ }
+
+ public static List<Permission> getAdminApplicationPermissions(
+ Set<org.onosproject.security.Permission> permissions) {
+ List<Permission> perms = Lists.newArrayList();
+ perms.addAll(defaultPermissions);
+ perms.addAll(adminServicePermissions);
+ for (AppPermission.Type perm : serviceDirectory.keySet()) {
+ perms.add(new AppPermission(perm));
+ }
+ perms.addAll(convertToJavaPermissions(permissions));
+ return optimizePermissions(perms);
+ }
+
+ public static List<Permission> convertToJavaPermissions(Set<org.onosproject.security.Permission> permissions) {
+ List<Permission> result = Lists.newArrayList();
+ for (org.onosproject.security.Permission perm : permissions) {
+ Permission javaPerm = getPermission(perm);
+ if (javaPerm != null) {
+ if (javaPerm instanceof AppPermission) {
+ if (((AppPermission) javaPerm).getType() != null) {
+ AppPermission ap = (AppPermission) javaPerm;
+ result.add(ap);
+ if (serviceDirectory.containsKey(ap.getType())) {
+ for (String service : serviceDirectory.get(ap.getType())) {
+ result.add(new ServicePermission(service, ServicePermission.GET));
+ }
+ }
+ }
+ } else if (javaPerm instanceof ServicePermission) {
+ if (!javaPerm.getName().contains(SecurityAdminService.class.getName())) {
+ result.add(javaPerm);
+ }
+ } else {
+ result.add(javaPerm);
+ }
+
+ }
+ }
+ return result;
+ }
+
+ public static Set<org.onosproject.security.Permission> convertToOnosPermissions(List<Permission> permissions) {
+ Set<org.onosproject.security.Permission> result = Sets.newHashSet();
+ for (Permission perm : permissions) {
+ org.onosproject.security.Permission onosPerm = getOnosPermission(perm);
+ if (onosPerm != null) {
+ result.add(onosPerm);
+ }
+ }
+ return result;
+ }
+
+ public static List<Permission> getDefaultPerms() {
+ List<Permission> permSet = Lists.newArrayList();
+ permSet.add(new PackagePermission("*", PackagePermission.EXPORTONLY));
+ permSet.add(new PackagePermission("*", PackagePermission.IMPORT));
+ permSet.add(new AdaptPermission("*", AdaptPermission.ADAPT));
+ permSet.add(new ConfigurationPermission("*", ConfigurationPermission.CONFIGURE));
+ return permSet;
+ }
+
+ private static List<Permission> getAdminDefaultPerms() {
+ List<Permission> permSet = Lists.newArrayList();
+ permSet.add(new ServicePermission(ApplicationAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(ClusterAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(MastershipAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(DeviceAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(HostAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(LinkAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(DriverAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(StorageAdminService.class.getName(), ServicePermission.GET));
+// permSet.add(new ServicePermission(LabelResourceAdminService.class.getName(), ServicePermission.GET));
+// permSet.add(new ServicePermission(TunnelAdminService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(ApplicationService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(ComponentConfigService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(CoreService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(ClusterService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(LeadershipService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(MastershipService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(DeviceService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(DeviceClockService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(DriverService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(FlowRuleService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(FlowObjectiveService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(GroupService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(HostService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(IntentService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(IntentClockService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(IntentExtensionService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(PartitionService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(LinkService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(LinkResourceService.class.getName(), ServicePermission.GET));
+// permSet.add(new ServicePermission(LabelResourceService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(PacketService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(ProxyArpService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(StatisticService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(PathService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(TopologyService.class.getName(), ServicePermission.GET));
+// permSet.add(new ServicePermission(TunnelService.class.getName(), ServicePermission.GET));
+ permSet.add(new ServicePermission(StorageService.class.getName(), ServicePermission.GET));
+ return permSet;
+ }
+
+ public static Set<String> getNBServiceList() {
+ Set<String> permString = new HashSet<>();
+ for (Permission perm : getAdminDefaultPerms()) {
+ permString.add(perm.getName());
+ }
+ return permString;
+ }
+
+ private static ConcurrentHashMap<AppPermission.Type, Set<String>> getServiceDirectory() {
+
+ ConcurrentHashMap<AppPermission.Type, Set<String>> serviceDirectory = new ConcurrentHashMap<>();
+
+ serviceDirectory.put(APP_READ, ImmutableSet.of(
+ ApplicationService.class.getName(), CoreService.class.getName()));
+ serviceDirectory.put(APP_EVENT, ImmutableSet.of(
+ ApplicationService.class.getName(), CoreService.class.getName()));
+ serviceDirectory.put(CONFIG_READ, ImmutableSet.of(
+ ComponentConfigService.class.getName()));
+ serviceDirectory.put(CONFIG_WRITE, ImmutableSet.of(
+ ComponentConfigService.class.getName()));
+ serviceDirectory.put(CLUSTER_READ, ImmutableSet.of(
+ ClusterService.class.getName(), LeadershipService.class.getName(),
+ MastershipService.class.getName()));
+ serviceDirectory.put(CLUSTER_WRITE, ImmutableSet.of(
+ LeadershipService.class.getName(), MastershipService.class.getName()));
+ serviceDirectory.put(CLUSTER_EVENT, ImmutableSet.of(
+ ClusterService.class.getName(), LeadershipService.class.getName(),
+ MastershipService.class.getName()));
+ serviceDirectory.put(DEVICE_READ, ImmutableSet.of(
+ DeviceService.class.getName(), DeviceClockService.class.getName()));
+ serviceDirectory.put(DEVICE_EVENT, ImmutableSet.of(
+ DeviceService.class.getName()));
+ serviceDirectory.put(DRIVER_READ, ImmutableSet.of(
+ DriverService.class.getName()));
+ serviceDirectory.put(DRIVER_WRITE, ImmutableSet.of(
+ DriverService.class.getName()));
+ serviceDirectory.put(FLOWRULE_READ, ImmutableSet.of(
+ FlowRuleService.class.getName()));
+ serviceDirectory.put(FLOWRULE_WRITE, ImmutableSet.of(
+ FlowRuleService.class.getName(), FlowObjectiveService.class.getName()));
+ serviceDirectory.put(FLOWRULE_EVENT, ImmutableSet.of(
+ FlowRuleService.class.getName()));
+ serviceDirectory.put(GROUP_READ, ImmutableSet.of(
+ GroupService.class.getName()));
+ serviceDirectory.put(GROUP_WRITE, ImmutableSet.of(
+ GroupService.class.getName()));
+ serviceDirectory.put(GROUP_EVENT, ImmutableSet.of(
+ GroupService.class.getName()));
+ serviceDirectory.put(HOST_READ, ImmutableSet.of(
+ HostService.class.getName()));
+ serviceDirectory.put(HOST_WRITE, ImmutableSet.of(
+ HostService.class.getName()));
+ serviceDirectory.put(HOST_EVENT, ImmutableSet.of(
+ HostService.class.getName()));
+ serviceDirectory.put(INTENT_READ, ImmutableSet.of(
+ IntentService.class.getName(), PartitionService.class.getName(),
+ IntentClockService.class.getName()));
+ serviceDirectory.put(INTENT_WRITE, ImmutableSet.of(
+ IntentService.class.getName()));
+ serviceDirectory.put(INTENT_EVENT, ImmutableSet.of(
+ IntentService.class.getName()));
+// serviceDirectory.put(LINK_READ, ImmutableSet.of(
+// LinkService.class.getName(), LinkResourceService.class.getName(),
+// LabelResourceService.class.getName()));
+// serviceDirectory.put(LINK_WRITE, ImmutableSet.of(
+// LinkResourceService.class.getName(), LabelResourceService.class.getName()));
+// serviceDirectory.put(LINK_EVENT, ImmutableSet.of(
+// LinkService.class.getName(), LinkResourceService.class.getName(),
+// LabelResourceService.class.getName()));
+ serviceDirectory.put(PACKET_READ, ImmutableSet.of(
+ PacketService.class.getName(), ProxyArpService.class.getName()));
+ serviceDirectory.put(PACKET_WRITE, ImmutableSet.of(
+ PacketService.class.getName(), ProxyArpService.class.getName()));
+ serviceDirectory.put(PACKET_EVENT, ImmutableSet.of(
+ PacketService.class.getName()));
+ serviceDirectory.put(STATISTIC_READ, ImmutableSet.of(
+ StatisticService.class.getName()));
+ serviceDirectory.put(TOPOLOGY_READ, ImmutableSet.of(
+ TopologyService.class.getName(), PathService.class.getName()));
+ serviceDirectory.put(TOPOLOGY_EVENT, ImmutableSet.of(
+ TopologyService.class.getName()));
+// serviceDirectory.put(TUNNEL_READ, ImmutableSet.of(
+// TunnelService.class.getName()));
+// serviceDirectory.put(TUNNEL_WRITE, ImmutableSet.of(
+// TunnelService.class.getName()));
+// serviceDirectory.put(TUNNEL_EVENT, ImmutableSet.of(
+// TunnelService.class.getName()));
+ serviceDirectory.put(STORAGE_WRITE, ImmutableSet.of(
+ StorageService.class.getName()));
+
+ return serviceDirectory;
+ }
+
+
+ public static org.onosproject.security.Permission getOnosPermission(Permission permission) {
+ if (permission instanceof AppPermission) {
+ return new org.onosproject.security.Permission(AppPermission.class.getName(), permission.getName(), "");
+ } else if (permission instanceof FilePermission) {
+ return new org.onosproject.security.Permission(
+ FilePermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof SerializablePermission) {
+ return new org.onosproject.security.Permission(
+ SerializablePermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof NetPermission) {
+ return new org.onosproject.security.Permission(
+ NetPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof RuntimePermission) {
+ return new org.onosproject.security.Permission(
+ RuntimePermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof SocketPermission) {
+ return new org.onosproject.security.Permission(
+ SocketPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof SQLPermission) {
+ return new org.onosproject.security.Permission(
+ SQLPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof PropertyPermission) {
+ return new org.onosproject.security.Permission(
+ PropertyPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof LoggingPermission) {
+ return new org.onosproject.security.Permission(
+ LoggingPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof SSLPermission) {
+ return new org.onosproject.security.Permission(
+ SSLPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof AuthPermission) {
+ return new org.onosproject.security.Permission(
+ AuthPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof PrivateCredentialPermission) {
+ return new org.onosproject.security.Permission(
+ PrivateCredentialPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof DelegationPermission) {
+ return new org.onosproject.security.Permission(
+ DelegationPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof javax.security.auth.kerberos.ServicePermission) {
+ return new org.onosproject.security.Permission(
+ javax.security.auth.kerberos.ServicePermission.class.getName(), permission.getName(),
+ permission.getActions());
+ } else if (permission instanceof AudioPermission) {
+ return new org.onosproject.security.Permission(
+ AudioPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof AdaptPermission) {
+ return new org.onosproject.security.Permission(
+ AdaptPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof BundlePermission) {
+ return new org.onosproject.security.Permission(
+ BundlePermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof CapabilityPermission) {
+ return new org.onosproject.security.Permission(
+ CapabilityPermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof PackagePermission) {
+ return new org.onosproject.security.Permission(
+ PackagePermission.class.getName(), permission.getName(), permission.getActions());
+ } else if (permission instanceof ServicePermission) {
+ return new org.onosproject.security.Permission(
+ ServicePermission.class.getName(), permission.getName(), permission.getActions());
+ }
+ return null;
+ }
+
+ private static Permission getPermission(org.onosproject.security.Permission permission) {
+
+ String classname = permission.getClassName();
+ String name = permission.getName();
+ String actions = permission.getActions();
+
+ if (classname == null || name == null) {
+ return null;
+ }
+ classname = classname.trim();
+ name = name.trim();
+ actions = actions.trim();
+
+ if (AppPermission.class.getName().equals(classname)) {
+ return new AppPermission(name);
+ } else if (FilePermission.class.getName().equals(classname)) {
+ return new FilePermission(name, actions);
+ } else if (SerializablePermission.class.getName().equals(classname)) {
+ return new SerializablePermission(name, actions);
+ } else if (NetPermission.class.getName().equals(classname)) {
+ return new NetPermission(name, actions);
+ } else if (RuntimePermission.class.getName().equals(classname)) {
+ return new RuntimePermission(name, actions);
+ } else if (SocketPermission.class.getName().equals(classname)) {
+ return new SocketPermission(name, actions);
+ } else if (SQLPermission.class.getName().equals(classname)) {
+ return new SQLPermission(name, actions);
+ } else if (PropertyPermission.class.getName().equals(classname)) {
+ return new PropertyPermission(name, actions);
+ } else if (LoggingPermission.class.getName().equals(classname)) {
+ return new LoggingPermission(name, actions);
+ } else if (SSLPermission.class.getName().equals(classname)) {
+ return new SSLPermission(name, actions);
+ } else if (AuthPermission.class.getName().equals(classname)) {
+ return new AuthPermission(name, actions);
+ } else if (PrivateCredentialPermission.class.getName().equals(classname)) {
+ return new PrivateCredentialPermission(name, actions);
+ } else if (DelegationPermission.class.getName().equals(classname)) {
+ return new DelegationPermission(name, actions);
+ } else if (javax.security.auth.kerberos.ServicePermission.class.getName().equals(classname)) {
+ return new javax.security.auth.kerberos.ServicePermission(name, actions);
+ } else if (AudioPermission.class.getName().equals(classname)) {
+ return new AudioPermission(name, actions);
+ } else if (AdaptPermission.class.getName().equals(classname)) {
+ return new AdaptPermission(name, actions);
+ } else if (BundlePermission.class.getName().equals(classname)) {
+ return new BundlePermission(name, actions);
+ } else if (CapabilityPermission.class.getName().equals(classname)) {
+ return new CapabilityPermission(name, actions);
+ } else if (PackagePermission.class.getName().equals(classname)) {
+ return new PackagePermission(name, actions);
+ } else if (ServicePermission.class.getName().equals(classname)) {
+ return new ServicePermission(name, actions);
+ }
+
+ //AllPermission, SecurityPermission, UnresolvedPermission
+ //AWTPermission, AdminPermission(osgi), ReflectPermission not allowed
+ return null;
+
+ }
+ private static List<Permission> optimizePermissions(List<Permission> perms) {
+ Permissions permissions = listToPermissions(perms);
+ return permissionsToList(permissions);
+ }
+
+ private static List<Permission> permissionsToList(Permissions perms) {
+ List<Permission> permissions = new ArrayList<>();
+ Enumeration<Permission> e = perms.elements();
+ while (e.hasMoreElements()) {
+ permissions.add(e.nextElement());
+ }
+ return permissions;
+ }
+
+ private static Permissions listToPermissions(List<Permission> perms) {
+ Permissions permissions = new Permissions();
+ for (Permission perm : perms) {
+ permissions.add(perm);
+ }
+ return permissions;
+ }
+}
+
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/SecurityModeManager.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/SecurityModeManager.java
new file mode 100644
index 00000000..30b0f8bb
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/SecurityModeManager.java
@@ -0,0 +1,289 @@
+package org.onosproject.security.impl;
+
+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.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.security.AppPermission;
+import org.onosproject.security.SecurityAdminService;
+import org.onosproject.security.store.SecurityModeEvent;
+import org.onosproject.security.store.SecurityModeListener;
+import org.onosproject.security.store.SecurityModeStore;
+import org.onosproject.security.store.SecurityModeStoreDelegate;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServicePermission;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+import java.security.AccessControlException;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+
+/**
+ * Security-Mode ONOS management implementation.
+ */
+
+@Component(immediate = true)
+@Service
+public class SecurityModeManager implements SecurityAdminService {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected SecurityModeStore store;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationAdminService appAdminService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LogReaderService logReaderService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ private final Logger log = getLogger(getClass());
+
+ protected final ListenerRegistry<SecurityModeEvent, SecurityModeListener>
+ listenerRegistry = new ListenerRegistry<>();
+
+ private final SecurityModeStoreDelegate delegate = new InternalStoreDelegate();
+
+ private SecurityLogListener securityLogListener = new SecurityLogListener();
+
+ private PermissionAdmin permissionAdmin = getPermissionAdmin();
+
+
+ @Activate
+ public void activate() {
+
+ eventDispatcher.addSink(SecurityModeEvent.class, listenerRegistry);
+ // add Listeners
+ logReaderService.addLogListener(securityLogListener);
+
+ store.setDelegate(delegate);
+
+ if (System.getSecurityManager() == null) {
+ log.warn("J2EE security manager is disabled.");
+ deactivate();
+ return;
+ }
+ if (permissionAdmin == null) {
+ log.warn("Permission Admin not found.");
+ deactivate();
+ return;
+ }
+
+ log.info("Security-Mode Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(SecurityModeEvent.class);
+ logReaderService.removeLogListener(securityLogListener);
+ store.unsetDelegate(delegate);
+ log.info("Stopped");
+
+ }
+
+ @Override
+ public boolean isSecured(ApplicationId appId) {
+ if (store.getState(appId) == null) {
+ store.registerApplication(appId);
+ }
+ return store.isSecured(appId);
+ }
+
+
+ @Override
+ public void review(ApplicationId appId) {
+ if (store.getState(appId) == null) {
+ store.registerApplication(appId);
+ }
+ store.reviewPolicy(appId);
+ }
+
+ @Override
+ public void acceptPolicy(ApplicationId appId) {
+ if (store.getState(appId) == null) {
+ store.registerApplication(appId);
+ }
+ store.acceptPolicy(appId, DefaultPolicyBuilder.convertToOnosPermissions(getMaximumPermissions(appId)));
+ }
+
+ @Override
+ public void register(ApplicationId appId) {
+ store.registerApplication(appId);
+ }
+
+ @Override
+ public Map<Integer, List<Permission>> getPrintableSpecifiedPermissions(ApplicationId appId) {
+ return getPrintablePermissionMap(getMaximumPermissions(appId));
+ }
+
+ @Override
+ public Map<Integer, List<Permission>> getPrintableGrantedPermissions(ApplicationId appId) {
+ return getPrintablePermissionMap(
+ DefaultPolicyBuilder.convertToJavaPermissions(store.getGrantedPermissions(appId)));
+ }
+
+ @Override
+ public Map<Integer, List<Permission>> getPrintableRequestedPermissions(ApplicationId appId) {
+ return getPrintablePermissionMap(
+ DefaultPolicyBuilder.convertToJavaPermissions(store.getRequestedPermissions(appId)));
+ }
+
+ private class SecurityLogListener implements LogListener {
+ @Override
+ public void logged(LogEntry entry) {
+ if (entry.getException() != null &&
+ entry.getException() instanceof AccessControlException) {
+ String location = entry.getBundle().getLocation();
+ Permission javaPerm =
+ ((AccessControlException) entry.getException()).getPermission();
+ org.onosproject.security.Permission permission = DefaultPolicyBuilder.getOnosPermission(javaPerm);
+ if (permission == null) {
+ log.warn("Unsupported permission requested.");
+ return;
+ }
+ store.getApplicationIds(location).stream().filter(
+ appId -> store.isSecured(appId) &&
+ appAdminService.getState(appId) == ApplicationState.ACTIVE).forEach(appId -> {
+ store.requestPermission(appId, permission);
+ print("[POLICY VIOLATION] APP: %s / Bundle: %s / Permission: %s ",
+ appId.name(), location, permission.toString());
+ });
+ }
+ }
+ }
+
+ private class InternalStoreDelegate implements SecurityModeStoreDelegate {
+ @Override
+ public void notify(SecurityModeEvent event) {
+ if (event.type() == SecurityModeEvent.Type.POLICY_ACCEPTED) {
+ setLocalPermissions(event.subject());
+ log.info("{} POLICY ACCEPTED and ENFORCED", event.subject().name());
+ } else if (event.type() == SecurityModeEvent.Type.POLICY_VIOLATED) {
+ log.info("{} POLICY VIOLATED", event.subject().name());
+ } else if (event.type() == SecurityModeEvent.Type.POLICY_REVIEWED) {
+ log.info("{} POLICY REVIEWED", event.subject().name());
+ }
+ eventDispatcher.post(event);
+ }
+ }
+
+ /**
+ * TYPES.
+ * 0 - APP_PERM
+ * 1 - ADMIN SERVICE
+ * 2 - NB_SERVICE
+ * 3 - ETC_SERVICE
+ * 4 - ETC
+ * @param perms
+ */
+ private Map<Integer, List<Permission>> getPrintablePermissionMap(List<Permission> perms) {
+ ConcurrentHashMap<Integer, List<Permission>> sortedMap = new ConcurrentHashMap<>();
+ sortedMap.put(0, new ArrayList());
+ sortedMap.put(1, new ArrayList());
+ sortedMap.put(2, new ArrayList());
+ sortedMap.put(3, new ArrayList());
+ sortedMap.put(4, new ArrayList());
+ for (Permission perm : perms) {
+ if (perm instanceof ServicePermission) {
+ if (DefaultPolicyBuilder.getNBServiceList().contains(perm.getName())) {
+ if (perm.getName().contains("Admin")) {
+ sortedMap.get(1).add(perm);
+ } else {
+ sortedMap.get(2).add(perm);
+ }
+ } else {
+ sortedMap.get(3).add(perm);
+ }
+ } else if (perm instanceof AppPermission) {
+ sortedMap.get(0).add(perm);
+ } else {
+ sortedMap.get(4).add(perm);
+ }
+ }
+ return sortedMap;
+ }
+
+ private void setLocalPermissions(ApplicationId applicationId) {
+ for (String location : store.getBundleLocations(applicationId)) {
+ permissionAdmin.setPermissions(location, permissionsToInfo(store.getGrantedPermissions(applicationId)));
+ }
+ }
+
+ private PermissionInfo[] permissionsToInfo(Set<org.onosproject.security.Permission> permissions) {
+ List<PermissionInfo> result = Lists.newArrayList();
+ for (org.onosproject.security.Permission perm : permissions) {
+ result.add(new PermissionInfo(perm.getClassName(), perm.getName(), perm.getActions()));
+ }
+ PermissionInfo[] permissionInfos = new PermissionInfo[result.size()];
+ return result.toArray(permissionInfos);
+ }
+
+
+
+ private List<Permission> getMaximumPermissions(ApplicationId appId) {
+ Application app = appAdminService.getApplication(appId);
+ if (app == null) {
+ print("Unknown application.");
+ return null;
+ }
+ List<Permission> appPerms;
+ switch (app.role()) {
+ case ADMIN:
+ appPerms = DefaultPolicyBuilder.getAdminApplicationPermissions(app.permissions());
+ break;
+ case USER:
+ appPerms = DefaultPolicyBuilder.getUserApplicationPermissions(app.permissions());
+ break;
+ case UNSPECIFIED:
+ default:
+ appPerms = DefaultPolicyBuilder.getDefaultPerms();
+ break;
+ }
+
+ return appPerms;
+ }
+
+
+ private void print(String format, Object... args) {
+ System.out.println(String.format("SM-ONOS: " + format, args));
+ log.warn(String.format(format, args));
+ }
+
+ private PermissionAdmin getPermissionAdmin() {
+ BundleContext context = getBundleContext();
+ return (PermissionAdmin) context.getService(context.getServiceReference(PermissionAdmin.class.getName()));
+ }
+
+ private BundleContext getBundleContext() {
+ return FrameworkUtil.getBundle(this.getClass()).getBundleContext();
+
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/package-info.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/impl/package-info.java
new file mode 100644
index 00000000..387f6ecf
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/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 security mode.
+ */
+package org.onosproject.security.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/DistributedSecurityModeStore.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/DistributedSecurityModeStore.java
new file mode 100644
index 00000000..ac16966c
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/DistributedSecurityModeStore.java
@@ -0,0 +1,315 @@
+/*
+ * 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.security.store;
+
+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.apache.felix.scr.annotations.Service;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.LogicalClockService;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static org.onosproject.security.store.SecurityModeState.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages application permissions granted/requested to applications.
+ * Uses both gossip-based and RAFT-based distributed data store.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedSecurityModeStore
+ extends AbstractStore<SecurityModeEvent, SecurityModeStoreDelegate>
+ implements SecurityModeStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private ConsistentMap<ApplicationId, SecurityInfo> states;
+ private EventuallyConsistentMap<ApplicationId, Set<Permission>> violations;
+
+ private ConcurrentHashMap<String, Set<ApplicationId>> localBundleAppDirectory;
+ private ConcurrentHashMap<ApplicationId, Set<String>> localAppBundleDirectory;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LogicalClockService clockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationAdminService applicationAdminService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FeaturesService featuresService;
+
+ private static final Serializer STATE_SERIALIZER = Serializer.using(new KryoNamespace.Builder()
+ .register(KryoNamespaces.API)
+ .register(SecurityModeState.class)
+ .register(SecurityInfo.class)
+ .register(Permission.class)
+ .build());
+
+ private static final KryoNamespace.Builder VIOLATION_SERIALIZER = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(Permission.class);
+
+ @Activate
+ public void activate() {
+ states = storageService.<ApplicationId, SecurityInfo>consistentMapBuilder()
+ .withName("smonos-sdata")
+ .withSerializer(STATE_SERIALIZER)
+ .build();
+
+ states.addListener(new SecurityStateListener());
+
+ violations = storageService.<ApplicationId, Set<Permission>>eventuallyConsistentMapBuilder()
+ .withName("smonos-rperms")
+ .withSerializer(VIOLATION_SERIALIZER)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+
+ localBundleAppDirectory = new ConcurrentHashMap<>();
+ localAppBundleDirectory = new ConcurrentHashMap<>();
+
+ log.info("Started");
+
+ }
+
+ @Deactivate
+ public void deactivate() {
+ violations.destroy();
+ log.info("Stopped");
+ }
+
+
+ @Override
+ public Set<String> getBundleLocations(ApplicationId appId) {
+ Set<String> locations = localAppBundleDirectory.get(appId);
+ return locations != null ? locations : Sets.newHashSet();
+ }
+
+ @Override
+ public Set<ApplicationId> getApplicationIds(String location) {
+ Set<ApplicationId> appIds = localBundleAppDirectory.get(location);
+ return appIds != null ? appIds : Sets.newHashSet();
+ }
+
+ @Override
+ public Set<Permission> getRequestedPermissions(ApplicationId appId) {
+ Set<Permission> permissions = violations.get(appId);
+ return permissions != null ? permissions : ImmutableSet.of();
+ }
+
+ @Override
+ public Set<Permission> getGrantedPermissions(ApplicationId appId) {
+ return states.asJavaMap().getOrDefault(appId, new SecurityInfo(ImmutableSet.of(), null)).getPermissions();
+ }
+
+ @Override
+ public void requestPermission(ApplicationId appId, Permission permission) {
+
+ states.computeIf(appId, securityInfo -> (securityInfo == null || securityInfo.getState() != POLICY_VIOLATED),
+ (id, securityInfo) -> new SecurityInfo(securityInfo.getPermissions(), POLICY_VIOLATED));
+ violations.compute(appId, (k, v) -> v == null ? Sets.newHashSet(permission) : addAndGet(v, permission));
+ }
+
+ private Set<Permission> addAndGet(Set<Permission> oldSet, Permission newPerm) {
+ oldSet.add(newPerm);
+ return oldSet;
+ }
+
+ @Override
+ public boolean isSecured(ApplicationId appId) {
+ SecurityInfo info = states.get(appId).value();
+ return info == null ? false : info.getState().equals(SECURED);
+ }
+
+ @Override
+ public void reviewPolicy(ApplicationId appId) {
+ Application app = applicationAdminService.getApplication(appId);
+ if (app == null) {
+ log.warn("Unknown Application");
+ return;
+ }
+ states.computeIfPresent(appId, (applicationId, securityInfo) -> {
+ if (securityInfo.getState().equals(INSTALLED)) {
+ return new SecurityInfo(ImmutableSet.of(), REVIEWED);
+ }
+ return securityInfo;
+ });
+ }
+
+ @Override
+ public void acceptPolicy(ApplicationId appId, Set<Permission> permissionSet) {
+
+ Application app = applicationAdminService.getApplication(appId);
+ if (app == null) {
+ log.warn("Unknown Application");
+ return;
+ }
+
+ states.computeIf(appId,
+ securityInfo -> (securityInfo != null),
+ (id, securityInfo) -> {
+ switch (securityInfo.getState()) {
+ case POLICY_VIOLATED:
+ System.out.println(
+ "This application has violated the security policy. Please uninstall.");
+ return securityInfo;
+ case SECURED:
+ System.out.println(
+ "The policy has been accepted already. To review policy, review [app.name]");
+ return securityInfo;
+ case INSTALLED:
+ System.out.println("Please review the security policy prior to accept them");
+ log.warn("Application has not been reviewed");
+ return securityInfo;
+ case REVIEWED:
+ return new SecurityInfo(permissionSet, SECURED);
+ default:
+ return securityInfo;
+ }
+ });
+ }
+
+ private final class SecurityStateListener
+ implements MapEventListener<ApplicationId, SecurityInfo> {
+
+ @Override
+ public void event(MapEvent<ApplicationId, SecurityInfo> event) {
+
+ if (delegate == null) {
+ return;
+ }
+ ApplicationId appId = event.key();
+ SecurityInfo info = event.value().value();
+
+ if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
+ switch (info.getState()) {
+ case POLICY_VIOLATED:
+ notifyDelegate(new SecurityModeEvent(SecurityModeEvent.Type.POLICY_VIOLATED, appId));
+ break;
+ case SECURED:
+ notifyDelegate(new SecurityModeEvent(SecurityModeEvent.Type.POLICY_ACCEPTED, appId));
+ default:
+ break;
+ }
+ } else if (event.type() == MapEvent.Type.REMOVE) {
+ removeAppFromDirectories(appId);
+ }
+ }
+ }
+
+ private void removeAppFromDirectories(ApplicationId appId) {
+ for (String location : localAppBundleDirectory.get(appId)) {
+ localBundleAppDirectory.get(location).remove(appId);
+ }
+ violations.remove(appId);
+ states.remove(appId);
+ localAppBundleDirectory.remove(appId);
+ }
+
+ @Override
+ public boolean registerApplication(ApplicationId appId) {
+ Application app = applicationAdminService.getApplication(appId);
+ if (app == null) {
+ log.warn("Unknown application.");
+ return false;
+ }
+ localAppBundleDirectory.put(appId, getBundleLocations(app));
+ for (String location : localAppBundleDirectory.get(appId)) {
+ if (!localBundleAppDirectory.containsKey(location)) {
+ localBundleAppDirectory.put(location, new HashSet<>());
+ }
+ if (!localBundleAppDirectory.get(location).contains(appId)) {
+ localBundleAppDirectory.get(location).add(appId);
+ }
+ }
+ states.put(appId, new SecurityInfo(Sets.newHashSet(), INSTALLED));
+ return true;
+ }
+
+ @Override
+ public void unregisterApplication(ApplicationId appId) {
+ if (localAppBundleDirectory.containsKey(appId)) {
+ for (String location : localAppBundleDirectory.get(appId)) {
+ if (localBundleAppDirectory.get(location).size() == 1) {
+ localBundleAppDirectory.remove(location);
+ } else {
+ localBundleAppDirectory.get(location).remove(appId);
+ }
+ }
+ localAppBundleDirectory.remove(appId);
+ }
+ }
+
+ @Override
+ public SecurityModeState getState(ApplicationId appId) {
+ return states.asJavaMap().getOrDefault(appId, new SecurityInfo(null, null)).getState();
+ }
+
+ private Set<String> getBundleLocations(Application app) {
+ Set<String> locations = new HashSet<>();
+ for (String name : app.features()) {
+ try {
+ Feature feature = featuresService.getFeature(name);
+ locations.addAll(
+ feature.getBundles().stream().map(BundleInfo::getLocation).collect(Collectors.toList()));
+ } catch (Exception e) {
+ return locations;
+ }
+ }
+ return locations;
+ }
+
+ @Override
+ public void setDelegate(SecurityModeStoreDelegate delegate) {
+ super.setDelegate(delegate);
+ }
+
+ @Override
+ public void unsetDelegate(SecurityModeStoreDelegate delegate) {
+ super.setDelegate(delegate);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityInfo.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityInfo.java
new file mode 100644
index 00000000..4dcb7dae
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityInfo.java
@@ -0,0 +1,41 @@
+/*
+ * 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.security.store;
+
+import org.onosproject.security.Permission;
+
+import java.util.Set;
+
+/**
+ * Security-Mode ONOS security policy and state representation for distributed store.
+ */
+public class SecurityInfo {
+
+ protected Set<Permission> grantedPermissions;
+ protected SecurityModeState state;
+
+ public SecurityInfo(Set<Permission> perms, SecurityModeState state) {
+ this.grantedPermissions = perms;
+ this.state = state;
+ }
+ public Set<Permission> getPermissions() {
+ return grantedPermissions;
+ }
+ public SecurityModeState getState() {
+ return state;
+ }
+}
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeEvent.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeEvent.java
new file mode 100644
index 00000000..59da67b5
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeEvent.java
@@ -0,0 +1,48 @@
+/*
+ * 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.security.store;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Security-Mode ONOS notifications.
+ */
+public class SecurityModeEvent extends AbstractEvent<SecurityModeEvent.Type, ApplicationId> {
+
+ protected SecurityModeEvent(Type type, ApplicationId subject) {
+ super(type, subject);
+ }
+
+ public enum Type {
+
+ /**
+ * Signifies that security policy has been accepted.
+ */
+ POLICY_ACCEPTED,
+
+ /**
+ * Signifies that security policy has been reviewed.
+ */
+ POLICY_REVIEWED,
+
+ /**
+ * Signifies that application has violated security policy.
+ */
+ POLICY_VIOLATED,
+ }
+}
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeListener.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeListener.java
new file mode 100644
index 00000000..2745e0c0
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeListener.java
@@ -0,0 +1,25 @@
+/*
+ * 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.security.store;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Security-Mode ONOS event listener.
+ */
+public interface SecurityModeListener extends EventListener<SecurityModeEvent> {
+}
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeState.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeState.java
new file mode 100644
index 00000000..999c5f9f
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeState.java
@@ -0,0 +1,43 @@
+/*
+ * 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.security.store;
+
+/**
+ * Representation of Security-Mode ONOS application review state.
+ */
+public enum SecurityModeState {
+
+ /**
+ * Indicates that operator has accepted application security policy.
+ */
+ SECURED,
+
+ /**
+ * Indicates that application security policy has been reviewed.
+ */
+ REVIEWED,
+
+ /**
+ * Indicates that application has been installed.
+ */
+ INSTALLED,
+
+ /**
+ * Indicates that application has violated security policy.
+ */
+ POLICY_VIOLATED,
+}
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStore.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStore.java
new file mode 100644
index 00000000..7e6b6533
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStore.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.security.store;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.security.Permission;
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Security-Mode ONOS distributed store service.
+ */
+public interface SecurityModeStore extends Store<SecurityModeEvent, SecurityModeStoreDelegate> {
+
+ /**
+ * Updates the local bundle-application directories.
+ * @param appId application identifier
+ * @return true if successfully registered.
+ */
+ boolean registerApplication(ApplicationId appId);
+
+ /**
+ * Removes application info from the local bundle-application directories.
+ * @param appId application identifier
+ */
+ void unregisterApplication(ApplicationId appId);
+
+ /**
+ * Returns state of the specified application.
+ * @param appId application identifier
+ * @return Security-Mode State of application
+ */
+ SecurityModeState getState(ApplicationId appId);
+
+ /**
+ * Returns bundle locations of specified application.
+ * @param appId application identifier
+ * @return set of bundle location strings
+ */
+ Set<String> getBundleLocations(ApplicationId appId);
+
+ /**
+ * Returns application identifiers that are associated with given bundle location.
+ * @param location OSGi bundle location
+ * @return set of application identifiers
+ */
+ Set<ApplicationId> getApplicationIds(String location);
+
+ /**
+ * Returns a list of permissions that have been requested by given application.
+ * @param appId application identifier
+ * @return list of permissions
+ */
+ Set<Permission> getRequestedPermissions(ApplicationId appId);
+
+ /**
+ * Returns an array of permissions that have been granted to given application.
+ * @param appId application identifier
+ * @return array of permissionInfo
+ */
+ Set<Permission> getGrantedPermissions(ApplicationId appId);
+
+ /**
+ * Request permission that is required to run given application.
+ * @param appId application identifier
+ * @param permission permission
+ */
+ void requestPermission(ApplicationId appId, Permission permission);
+
+ /**
+ * Returns true if given application has been secured.
+ * @param appId application identifier
+ * @return true indicates secured
+ */
+ boolean isSecured(ApplicationId appId);
+
+ /**
+ * Notifies SM-ONOS that operator has reviewed the policy.
+ * @param appId application identifier
+ */
+ void reviewPolicy(ApplicationId appId);
+
+ /**
+ * Accept the current security policy of given application.
+ * @param appId application identifier
+ * @param permissionSet array of PermissionInfo
+ */
+ void acceptPolicy(ApplicationId appId, Set<Permission> permissionSet);
+} \ No newline at end of file
diff --git a/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStoreDelegate.java b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStoreDelegate.java
new file mode 100644
index 00000000..d933a148
--- /dev/null
+++ b/framework/src/onos/core/security/src/main/java/org/onosproject/security/store/SecurityModeStoreDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * 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.security.store;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Security-Mode distributed store delegate abstraction.
+ */
+public interface SecurityModeStoreDelegate extends StoreDelegate<SecurityModeEvent> {
+}
diff --git a/framework/src/onos/core/store/dist/pom.xml b/framework/src/onos/core/store/dist/pom.xml
new file mode 100644
index 00000000..eb9c38a5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-store</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core-dist</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS Gossip based distributed store subsystems</description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-math3</artifactId>
+ <version>3.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-netty</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-nio</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mapdb</groupId>
+ <artifactId>mapdb</artifactId>
+ <version>1.0.7</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- for shaded copycat -->
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-thirdparty</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java
new file mode 100644
index 00000000..6764c222
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java
@@ -0,0 +1,429 @@
+/*
+ * 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.store.app;
+
+import com.google.common.base.Charsets;
+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.onlab.util.KryoNamespace;
+import org.onosproject.app.ApplicationDescription;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationException;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStore;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.security.Permission;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.LogicalClockService;
+import org.onosproject.store.service.MultiValuedTimestamp;
+import org.onosproject.store.service.StorageException;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Function;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onlab.util.Tools.randomDelay;
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.store.app.GossipApplicationStore.InternalState.*;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of applications in a distributed data store that uses
+ * optimistic replication and gossip based anti-entropy techniques.
+ */
+@Component(immediate = true)
+@Service
+public class GossipApplicationStore extends ApplicationArchive
+ implements ApplicationStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
+
+ private static final int MAX_LOAD_RETRIES = 5;
+ private static final int RETRY_DELAY_MS = 2_000;
+
+ private static final int FETCH_TIMEOUT_MS = 10_000;
+
+ public enum InternalState {
+ INSTALLED, ACTIVATED, DEACTIVATED
+ }
+
+ private ScheduledExecutorService executor;
+ private ExecutorService messageHandlingExecutor;
+
+ private EventuallyConsistentMap<ApplicationId, Application> apps;
+ private EventuallyConsistentMap<Application, InternalState> states;
+ private EventuallyConsistentMap<Application, Set<Permission>> permissions;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LogicalClockService clockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationIdStore idStore;
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(MultiValuedTimestamp.class)
+ .register(InternalState.class);
+
+ executor = Executors.newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store"));
+
+ messageHandlingExecutor = Executors.newSingleThreadExecutor(
+ groupedThreads("onos/store/app", "message-handler"));
+
+ clusterCommunicator.<String, byte[]>addSubscriber(APP_BITS_REQUEST,
+ bytes -> new String(bytes, Charsets.UTF_8),
+ name -> {
+ try {
+ return toByteArray(getApplicationInputStream(name));
+ } catch (IOException e) {
+ throw new StorageException(e);
+ }
+ },
+ Function.identity(),
+ messageHandlingExecutor);
+
+ // FIXME: Consider consolidating into a single map.
+
+ apps = storageService.<ApplicationId, Application>eventuallyConsistentMapBuilder()
+ .withName("apps")
+ .withSerializer(serializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+
+ states = storageService.<Application, InternalState>eventuallyConsistentMapBuilder()
+ .withName("app-states")
+ .withSerializer(serializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+
+ states.addListener(new InternalAppStatesListener());
+
+ permissions = storageService.<Application, Set<Permission>>eventuallyConsistentMapBuilder()
+ .withName("app-permissions")
+ .withSerializer(serializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+
+ log.info("Started");
+ }
+
+ /**
+ * Loads the application inventory from the disk and activates apps if
+ * they are marked to be active.
+ */
+ private void loadFromDisk() {
+ for (String name : getApplicationNames()) {
+ for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
+ try {
+ Application app = create(getApplicationDescription(name), false);
+ if (app != null && isActive(app.id().name())) {
+ activate(app.id(), false);
+ // load app permissions
+ }
+ } catch (Exception e) {
+ log.warn("Unable to load application {} from disk; retrying", name);
+ randomDelay(RETRY_DELAY_MS); // FIXME: This is a deliberate hack; fix in Drake
+ }
+ }
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
+ messageHandlingExecutor.shutdown();
+ executor.shutdown();
+ apps.destroy();
+ states.destroy();
+ permissions.destroy();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void setDelegate(ApplicationStoreDelegate delegate) {
+ super.setDelegate(delegate);
+ loadFromDisk();
+// executor.schedule(this::pruneUninstalledApps, LOAD_TIMEOUT_MS, MILLISECONDS);
+ }
+
+ @Override
+ public Set<Application> getApplications() {
+ return ImmutableSet.copyOf(apps.values());
+ }
+
+ @Override
+ public ApplicationId getId(String name) {
+ return idStore.getAppId(name);
+ }
+
+ @Override
+ public Application getApplication(ApplicationId appId) {
+ return apps.get(appId);
+ }
+
+ @Override
+ public ApplicationState getState(ApplicationId appId) {
+ Application app = apps.get(appId);
+ InternalState s = app == null ? null : states.get(app);
+ return s == null ? null : s == ACTIVATED ?
+ ApplicationState.ACTIVE : ApplicationState.INSTALLED;
+ }
+
+ @Override
+ public Application create(InputStream appDescStream) {
+ ApplicationDescription appDesc = saveApplication(appDescStream);
+ return create(appDesc, true);
+ }
+
+ private Application create(ApplicationDescription appDesc, boolean updateTime) {
+ Application app = registerApp(appDesc);
+ if (updateTime) {
+ updateTime(app.id().name());
+ }
+ apps.put(app.id(), app);
+ states.put(app, INSTALLED);
+ return app;
+ }
+
+ @Override
+ public void remove(ApplicationId appId) {
+ Application app = apps.get(appId);
+ if (app != null) {
+ apps.remove(appId);
+ states.remove(app);
+ permissions.remove(app);
+ }
+ }
+
+ @Override
+ public void activate(ApplicationId appId) {
+ activate(appId, true);
+ }
+
+ private void activate(ApplicationId appId, boolean updateTime) {
+ Application app = apps.get(appId);
+ if (app != null) {
+ if (updateTime) {
+ updateTime(appId.name());
+ }
+ states.put(app, ACTIVATED);
+ }
+ }
+
+ @Override
+ public void deactivate(ApplicationId appId) {
+ Application app = apps.get(appId);
+ if (app != null) {
+ updateTime(appId.name());
+ states.put(app, DEACTIVATED);
+ }
+ }
+
+ @Override
+ public Set<Permission> getPermissions(ApplicationId appId) {
+ Application app = apps.get(appId);
+ return app != null ? permissions.get(app) : null;
+ }
+
+ @Override
+ public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+ Application app = getApplication(appId);
+ if (app != null) {
+ this.permissions.put(app, permissions);
+ delegate.notify(new ApplicationEvent(APP_PERMISSIONS_CHANGED, app));
+ }
+ }
+
+ /**
+ * Listener to application state distributed map changes.
+ */
+ private final class InternalAppStatesListener
+ implements EventuallyConsistentMapListener<Application, InternalState> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<Application, InternalState> event) {
+ // If we do not have a delegate, refuse to process any events entirely.
+ // This is to allow the anti-entropy to kick in and process the events
+ // perhaps a bit later, but with opportunity to notify delegate.
+ if (delegate == null) {
+ return;
+ }
+
+ Application app = event.key();
+ InternalState state = event.value();
+
+ if (event.type() == PUT) {
+ if (state == INSTALLED) {
+ fetchBitsIfNeeded(app);
+ delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
+
+ } else if (state == ACTIVATED) {
+ installAppIfNeeded(app);
+ setActive(app.id().name());
+ delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
+
+ } else if (state == DEACTIVATED) {
+ clearActive(app.id().name());
+ delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
+ }
+ } else if (event.type() == REMOVE) {
+ delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app));
+ purgeApplication(app.id().name());
+ }
+ }
+ }
+
+ /**
+ * Determines if the application bits are available locally.
+ */
+ private boolean appBitsAvailable(Application app) {
+ try {
+ ApplicationDescription appDesc = getApplicationDescription(app.id().name());
+ return appDesc.version().equals(app.version());
+ } catch (ApplicationException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Fetches the bits from the cluster peers if necessary.
+ */
+ private void fetchBitsIfNeeded(Application app) {
+ if (!appBitsAvailable(app)) {
+ fetchBits(app);
+ }
+ }
+
+ /**
+ * Installs the application if necessary from the application peers.
+ */
+ private void installAppIfNeeded(Application app) {
+ if (!appBitsAvailable(app)) {
+ fetchBits(app);
+ delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
+ }
+ }
+
+ /**
+ * Fetches the bits from the cluster peers.
+ */
+ private void fetchBits(Application app) {
+ ControllerNode localNode = clusterService.getLocalNode();
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // FIXME: send message with name & version to make sure we don't get served old bits
+
+ log.info("Downloading bits for application {}", app.id().name());
+ for (ControllerNode node : clusterService.getNodes()) {
+ if (latch.getCount() == 0) {
+ break;
+ }
+ if (node.equals(localNode)) {
+ continue;
+ }
+ clusterCommunicator.sendAndReceive(app.id().name(),
+ APP_BITS_REQUEST,
+ s -> s.getBytes(Charsets.UTF_8),
+ Function.identity(),
+ node.id())
+ .whenCompleteAsync((bits, error) -> {
+ if (error == null && latch.getCount() > 0) {
+ saveApplication(new ByteArrayInputStream(bits));
+ log.info("Downloaded bits for application {} from node {}",
+ app.id().name(), node.id());
+ latch.countDown();
+ } else if (error != null) {
+ log.warn("Unable to fetch bits for application {} from node {}",
+ app.id().name(), node.id());
+ }
+ }, executor);
+ }
+
+ try {
+ if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
+ log.warn("Unable to fetch bits for application {}", app.id().name());
+ }
+ } catch (InterruptedException e) {
+ log.warn("Interrupted while fetching bits for application {}", app.id().name());
+ }
+ }
+
+ /**
+ * Prunes applications which are not in the map, but are on disk.
+ */
+ private void pruneUninstalledApps() {
+ for (String name : getApplicationNames()) {
+ if (getApplication(getId(name)) == null) {
+ Application app = registerApp(getApplicationDescription(name));
+ delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app));
+ purgeApplication(app.id().name());
+ }
+ }
+ }
+
+ /**
+ * Produces a registered application from the supplied description.
+ */
+ private Application registerApp(ApplicationDescription appDesc) {
+ ApplicationId appId = idStore.registerApplication(appDesc.name());
+ return new DefaultApplication(appId, appDesc.version(), appDesc.description(),
+ appDesc.origin(), appDesc.role(), appDesc.permissions(),
+ appDesc.featuresRepo(), appDesc.features());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/package-info.java
new file mode 100644
index 00000000..b2a909ee
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/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 distributed applications store.
+ */
+package org.onosproject.store.app;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/GossipComponentConfigStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/GossipComponentConfigStore.java
new file mode 100644
index 00000000..bf992643
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/GossipComponentConfigStore.java
@@ -0,0 +1,120 @@
+/*
+ * 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.store.cfg;
+
+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.KryoNamespace;
+import org.onosproject.cfg.ComponentConfigEvent;
+import org.onosproject.cfg.ComponentConfigStore;
+import org.onosproject.cfg.ComponentConfigStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.LogicalClockService;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import static org.onosproject.cfg.ComponentConfigEvent.Type.PROPERTY_SET;
+import static org.onosproject.cfg.ComponentConfigEvent.Type.PROPERTY_UNSET;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of component configurations in a distributed data store
+ * that uses optimistic replication and gossip based anti-entropy techniques.
+ */
+@Component(immediate = true)
+@Service
+public class GossipComponentConfigStore
+ extends AbstractStore<ComponentConfigEvent, ComponentConfigStoreDelegate>
+ implements ComponentConfigStore {
+
+ private static final String SEP = "#";
+
+ private final Logger log = getLogger(getClass());
+
+ private EventuallyConsistentMap<String, String> properties;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LogicalClockService clockService;
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API);
+
+ properties = storageService.<String, String>eventuallyConsistentMapBuilder()
+ .withName("cfg")
+ .withSerializer(serializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+
+ properties.addListener(new InternalPropertiesListener());
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ properties.destroy();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void setProperty(String componentName, String name, String value) {
+ properties.put(key(componentName, name), value);
+
+ }
+
+ @Override
+ public void unsetProperty(String componentName, String name) {
+ properties.remove(key(componentName, name));
+ }
+
+ /**
+ * Listener to component configuration properties distributed map changes.
+ */
+ private final class InternalPropertiesListener
+ implements EventuallyConsistentMapListener<String, String> {
+
+ @Override
+ public void event(EventuallyConsistentMapEvent<String, String> event) {
+ String[] keys = event.key().split(SEP);
+ String value = event.value();
+ if (event.type() == PUT) {
+ delegate.notify(new ComponentConfigEvent(PROPERTY_SET, keys[0], keys[1], value));
+ } else if (event.type() == REMOVE) {
+ delegate.notify(new ComponentConfigEvent(PROPERTY_UNSET, keys[0], keys[1], null));
+ }
+ }
+ }
+
+ // Generates a key from component name and property name.
+ private String key(String componentName, String name) {
+ return componentName + SEP + name;
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/package-info.java
new file mode 100644
index 00000000..f8f8509a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cfg/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 distributed component configuration store.
+ */
+package org.onosproject.store.cfg;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.java
new file mode 100644
index 00000000..75f05a31
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.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.store.cluster.impl;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Cluster definition.
+ */
+public class ClusterDefinition {
+
+ private Set<NodeInfo> nodes;
+ private String ipPrefix;
+
+ /**
+ * Creates a new cluster definition.
+ * @param nodes cluster nodes information
+ * @param ipPrefix ip prefix common to all cluster nodes
+ * @return cluster definition
+ */
+ public static ClusterDefinition from(Set<NodeInfo> nodes, String ipPrefix) {
+ ClusterDefinition definition = new ClusterDefinition();
+ definition.ipPrefix = ipPrefix;
+ definition.nodes = ImmutableSet.copyOf(nodes);
+ return definition;
+ }
+
+ /**
+ * Returns set of cluster nodes info.
+ * @return cluster nodes info
+ */
+ public Set<NodeInfo> getNodes() {
+ return ImmutableSet.copyOf(nodes);
+ }
+
+ /**
+ * Returns ipPrefix in dotted decimal notion.
+ * @return ip prefix
+ */
+ public String getIpPrefix() {
+ return ipPrefix;
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java
new file mode 100644
index 00000000..4e28e3c2
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java
@@ -0,0 +1,179 @@
+package org.onosproject.store.cluster.impl;
+
+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.Service;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterDefinitionService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.consistent.impl.DatabaseDefinition;
+import org.onosproject.store.consistent.impl.DatabaseDefinitionStore;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.net.NetworkInterface.getNetworkInterfaces;
+import static java.util.Collections.list;
+import static org.onosproject.cluster.DefaultControllerNode.DEFAULT_PORT;
+import static org.onosproject.store.consistent.impl.DatabaseManager.PARTITION_DEFINITION_FILE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of ClusterDefinitionService.
+ */
+@Component(immediate = true)
+@Service
+public class ClusterDefinitionManager implements ClusterDefinitionService {
+
+ public static final String CLUSTER_DEFINITION_FILE = "../config/cluster.json";
+ private static final String ONOS_NIC = "ONOS_NIC";
+ private static final Logger log = getLogger(ClusterDefinitionManager.class);
+ private ControllerNode localNode;
+ private Set<ControllerNode> seedNodes;
+
+ @Activate
+ public void activate() {
+ File clusterDefinitionFile = new File(CLUSTER_DEFINITION_FILE);
+ ClusterDefinitionStore clusterDefinitionStore =
+ new ClusterDefinitionStore(clusterDefinitionFile.getPath());
+
+ if (!clusterDefinitionFile.exists()) {
+ createDefaultClusterDefinition(clusterDefinitionStore);
+ }
+
+ try {
+ ClusterDefinition clusterDefinition = clusterDefinitionStore.read();
+ establishSelfIdentity(clusterDefinition);
+ seedNodes = ImmutableSet
+ .copyOf(clusterDefinition.getNodes())
+ .stream()
+ .filter(n -> !localNode.id().equals(new NodeId(n.getId())))
+ .map(n -> new DefaultControllerNode(new NodeId(n.getId()),
+ IpAddress.valueOf(n.getIp()),
+ n.getTcpPort()))
+ .collect(Collectors.toSet());
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to read cluster definition.", e);
+ }
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public ControllerNode localNode() {
+ return localNode;
+ }
+
+ @Override
+ public Set<ControllerNode> seedNodes() {
+ return seedNodes;
+ }
+
+ @Override
+ public void formCluster(Set<ControllerNode> nodes, String ipPrefix) {
+ try {
+ Set<NodeInfo> infos = Sets.newHashSet();
+ nodes.forEach(n -> infos.add(NodeInfo.from(n.id().toString(),
+ n.ip().toString(),
+ n.tcpPort())));
+
+ ClusterDefinition cdef = ClusterDefinition.from(infos, ipPrefix);
+ new ClusterDefinitionStore(CLUSTER_DEFINITION_FILE).write(cdef);
+
+ DatabaseDefinition ddef = DatabaseDefinition.from(infos);
+ new DatabaseDefinitionStore(PARTITION_DEFINITION_FILE).write(ddef);
+ } catch (IOException e) {
+ log.error("Unable to form cluster", e);
+ }
+ }
+
+ private IpAddress findLocalIp(ClusterDefinition clusterDefinition) throws SocketException {
+ Enumeration<NetworkInterface> interfaces =
+ NetworkInterface.getNetworkInterfaces();
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface iface = interfaces.nextElement();
+ Enumeration<InetAddress> inetAddresses = iface.getInetAddresses();
+ while (inetAddresses.hasMoreElements()) {
+ IpAddress ip = IpAddress.valueOf(inetAddresses.nextElement());
+ if (clusterDefinition.getNodes().stream()
+ .map(NodeInfo::getIp)
+ .map(IpAddress::valueOf)
+ .anyMatch(nodeIp -> ip.equals(nodeIp))) {
+ return ip;
+ }
+ }
+ }
+ throw new IllegalStateException("Unable to determine local ip");
+ }
+
+ private void establishSelfIdentity(ClusterDefinition clusterDefinition) {
+ try {
+ IpAddress ip = findLocalIp(clusterDefinition);
+ localNode = new DefaultControllerNode(new NodeId(ip.toString()), ip);
+ } catch (SocketException e) {
+ throw new IllegalStateException("Cannot determine local IP", e);
+ }
+ }
+
+ private void createDefaultClusterDefinition(ClusterDefinitionStore store) {
+ // Assumes IPv4 is returned.
+ String ip = getSiteLocalAddress();
+ String ipPrefix = ip.replaceFirst("\\.[0-9]*$", ".*");
+ NodeInfo node = NodeInfo.from(ip, ip, DEFAULT_PORT);
+ try {
+ store.write(ClusterDefinition.from(ImmutableSet.of(node), ipPrefix));
+ } catch (IOException e) {
+ log.warn("Unable to write default cluster definition", e);
+ }
+ }
+
+ /**
+ * Returns the address that matches the IP prefix given in ONOS_NIC
+ * environment variable if one was specified, or the first site local
+ * address if one can be found or the loopback address otherwise.
+ *
+ * @return site-local address in string form
+ */
+ public static String getSiteLocalAddress() {
+ try {
+ String ipPrefix = System.getenv(ONOS_NIC);
+ for (NetworkInterface nif : list(getNetworkInterfaces())) {
+ for (InetAddress address : list(nif.getInetAddresses())) {
+ IpAddress ip = IpAddress.valueOf(address);
+ if (ipPrefix == null && address.isSiteLocalAddress() ||
+ ipPrefix != null && matchInterface(ip.toString(), ipPrefix)) {
+ return ip.toString();
+ }
+ }
+ }
+ } catch (SocketException e) {
+ log.error("Unable to get network interfaces", e);
+ }
+
+ return IpAddress.valueOf(InetAddress.getLoopbackAddress()).toString();
+ }
+
+ // Indicates whether the specified interface address matches the given prefix.
+ // FIXME: Add a facility to IpPrefix to make this more robust
+ private static boolean matchInterface(String ip, String ipPrefix) {
+ String s = ipPrefix.replaceAll("\\.\\*", "");
+ return ip.startsWith(s);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java
new file mode 100644
index 00000000..2a2f4dc4
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java
@@ -0,0 +1,63 @@
+/*
+ * 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.store.cluster.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Allows for reading and writing cluster definition as a JSON file.
+ */
+public class ClusterDefinitionStore {
+
+ private final File file;
+
+ /**
+ * Creates a reader/writer of the cluster definition file.
+ * @param filePath location of the definition file
+ */
+ public ClusterDefinitionStore(String filePath) {
+ file = new File(filePath);
+ }
+
+ /**
+ * Returns the cluster definition.
+ * @return cluster definition
+ * @throws IOException when I/O exception of some sort has occurred
+ */
+ public ClusterDefinition read() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(file, ClusterDefinition.class);
+ }
+
+ /**
+ * Writes the specified cluster definition to file.
+ * @param definition cluster definition
+ * @throws IOException when I/O exception of some sort has occurred
+ */
+ public void write(ClusterDefinition definition) throws IOException {
+ checkNotNull(definition);
+ // write back to file
+ Files.createParentDirs(file);
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(file, definition);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterManagementMessageSubjects.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterManagementMessageSubjects.java
new file mode 100644
index 00000000..918f7921
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterManagementMessageSubjects.java
@@ -0,0 +1,26 @@
+/*
+ * 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.store.cluster.impl;
+
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+//Not used right now
+public final class ClusterManagementMessageSubjects {
+ // avoid instantiation
+ private ClusterManagementMessageSubjects() {}
+
+ public static final MessageSubject CLUSTER_MEMBERSHIP_EVENT = new MessageSubject("CLUSTER_MEMBERSHIP_EVENT");
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEvent.java
new file mode 100644
index 00000000..c6428739
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEvent.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.store.cluster.impl;
+
+import org.onosproject.cluster.ControllerNode;
+
+//Not used right now
+/**
+ * Contains information that will be published when a cluster membership event occurs.
+ */
+public class ClusterMembershipEvent {
+
+ private final ClusterMembershipEventType type;
+ private final ControllerNode node;
+
+ public ClusterMembershipEvent(ClusterMembershipEventType type, ControllerNode node) {
+ this.type = type;
+ this.node = node;
+ }
+
+ public ClusterMembershipEventType type() {
+ return type;
+ }
+
+ public ControllerNode node() {
+ return node;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEventType.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEventType.java
new file mode 100644
index 00000000..a7f09c71
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterMembershipEventType.java
@@ -0,0 +1,24 @@
+/*
+ * 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.store.cluster.impl;
+
+//Not used right now
+public enum ClusterMembershipEventType {
+ NEW_MEMBER,
+ LEAVING_MEMBER,
+ UNREACHABLE_MEMBER,
+ HEART_BEAT,
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterNodesDelegate.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterNodesDelegate.java
new file mode 100644
index 00000000..7aeca72f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterNodesDelegate.java
@@ -0,0 +1,54 @@
+/*
+ * 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.store.cluster.impl;
+
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onlab.packet.IpAddress;
+
+// Not used right now
+/**
+ * Simple back interface through which connection manager can interact with
+ * the cluster store.
+ */
+public interface ClusterNodesDelegate {
+
+ /**
+ * Notifies about cluster node coming online.
+ *
+ * @param nodeId newly detected cluster node id
+ * @param ip node IP listen address
+ * @param tcpPort node TCP listen port
+ * @return the controller node
+ */
+ DefaultControllerNode nodeDetected(NodeId nodeId, IpAddress ip,
+ int tcpPort);
+
+ /**
+ * Notifies about cluster node going offline.
+ *
+ * @param nodeId identifier of the cluster node that vanished
+ */
+ void nodeVanished(NodeId nodeId);
+
+ /**
+ * Notifies about remote request to remove node from cluster.
+ *
+ * @param nodeId identifier of the cluster node that was removed
+ */
+ void nodeRemoved(NodeId nodeId);
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
new file mode 100644
index 00000000..859efebf
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
@@ -0,0 +1,280 @@
+/*
+ * 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.store.cluster.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.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterDefinitionService;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterStore;
+import org.onosproject.cluster.ClusterStoreDelegate;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.ControllerNode.State;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.Endpoint;
+import org.onosproject.store.cluster.messaging.MessagingService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+@Service
+/**
+ * Distributed cluster nodes store that employs an accrual failure
+ * detector to identify cluster member up/down status.
+ */
+public class DistributedClusterStore
+ extends AbstractStore<ClusterEvent, ClusterStoreDelegate>
+ implements ClusterStore {
+
+ private static final Logger log = getLogger(DistributedClusterStore.class);
+
+ public static final String HEARTBEAT_MESSAGE = "onos-cluster-heartbeat";
+
+ // TODO: make these configurable.
+ private static final int HEARTBEAT_INTERVAL_MS = 100;
+ private static final int PHI_FAILURE_THRESHOLD = 10;
+
+ private static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(HeartbeatMessage.class)
+ .build()
+ .populate(1);
+ }
+ };
+
+ private static final String INSTANCE_ID_NULL = "Instance ID cannot be null";
+
+ private final Map<NodeId, ControllerNode> allNodes = Maps.newConcurrentMap();
+ private final Map<NodeId, State> nodeStates = Maps.newConcurrentMap();
+ private final Map<NodeId, DateTime> nodeStateLastUpdatedTimes = Maps.newConcurrentMap();
+ private ScheduledExecutorService heartBeatSender = Executors.newSingleThreadScheduledExecutor(
+ groupedThreads("onos/cluster/membership", "heartbeat-sender"));
+ private ExecutorService heartBeatMessageHandler = Executors.newSingleThreadExecutor(
+ groupedThreads("onos/cluster/membership", "heartbeat-receiver"));
+
+ private PhiAccrualFailureDetector failureDetector;
+
+ private ControllerNode localNode;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterDefinitionService clusterDefinitionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MessagingService messagingService;
+
+ @Activate
+ public void activate() {
+ localNode = clusterDefinitionService.localNode();
+
+ messagingService.registerHandler(HEARTBEAT_MESSAGE,
+ new HeartbeatMessageHandler(), heartBeatMessageHandler);
+
+ failureDetector = new PhiAccrualFailureDetector();
+
+ heartBeatSender.scheduleWithFixedDelay(this::heartbeat, 0,
+ HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
+
+ addNode(localNode);
+ updateState(localNode.id(), State.ACTIVE);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ messagingService.unregisterHandler(HEARTBEAT_MESSAGE);
+ heartBeatSender.shutdownNow();
+ heartBeatMessageHandler.shutdownNow();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void setDelegate(ClusterStoreDelegate delegate) {
+ checkNotNull(delegate, "Delegate cannot be null");
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(ClusterStoreDelegate delegate) {
+ this.delegate = null;
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return this.delegate != null;
+ }
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return localNode;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return ImmutableSet.copyOf(allNodes.values());
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ return allNodes.get(nodeId);
+ }
+
+ @Override
+ public State getState(NodeId nodeId) {
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ return nodeStates.get(nodeId);
+ }
+
+ @Override
+ public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
+ ControllerNode node = new DefaultControllerNode(nodeId, ip, tcpPort);
+ addNode(node);
+ return node;
+ }
+
+ @Override
+ public void removeNode(NodeId nodeId) {
+ checkNotNull(nodeId, INSTANCE_ID_NULL);
+ ControllerNode node = allNodes.remove(nodeId);
+ if (node != null) {
+ nodeStates.remove(nodeId);
+ notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_REMOVED, node));
+ }
+ }
+
+ private void addNode(ControllerNode node) {
+ allNodes.put(node.id(), node);
+ updateState(node.id(), State.INACTIVE);
+ notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_ADDED, node));
+ }
+
+ private void updateState(NodeId nodeId, State newState) {
+ nodeStates.put(nodeId, newState);
+ nodeStateLastUpdatedTimes.put(nodeId, DateTime.now());
+ }
+
+ private void heartbeat() {
+ try {
+ Set<ControllerNode> peers = allNodes.values()
+ .stream()
+ .filter(node -> !(node.id().equals(localNode.id())))
+ .collect(Collectors.toSet());
+ byte[] hbMessagePayload = SERIALIZER.encode(new HeartbeatMessage(localNode, peers));
+ peers.forEach((node) -> {
+ heartbeatToPeer(hbMessagePayload, node);
+ State currentState = nodeStates.get(node.id());
+ double phi = failureDetector.phi(node.id());
+ if (phi >= PHI_FAILURE_THRESHOLD) {
+ if (currentState == State.ACTIVE) {
+ updateState(node.id(), State.INACTIVE);
+ notifyStateChange(node.id(), State.ACTIVE, State.INACTIVE);
+ }
+ } else {
+ if (currentState == State.INACTIVE) {
+ updateState(node.id(), State.ACTIVE);
+ notifyStateChange(node.id(), State.INACTIVE, State.ACTIVE);
+ }
+ }
+ });
+ } catch (Exception e) {
+ log.debug("Failed to send heartbeat", e);
+ }
+ }
+
+ private void notifyStateChange(NodeId nodeId, State oldState, State newState) {
+ ControllerNode node = allNodes.get(nodeId);
+ if (newState == State.ACTIVE) {
+ notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_ACTIVATED, node));
+ } else {
+ notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_DEACTIVATED, node));
+ }
+ }
+
+ private void heartbeatToPeer(byte[] messagePayload, ControllerNode peer) {
+ Endpoint remoteEp = new Endpoint(peer.ip(), peer.tcpPort());
+ messagingService.sendAsync(remoteEp, HEARTBEAT_MESSAGE, messagePayload).whenComplete((result, error) -> {
+ if (error != null) {
+ log.trace("Sending heartbeat to {} failed", remoteEp, error);
+ }
+ });
+ }
+
+ private class HeartbeatMessageHandler implements Consumer<byte[]> {
+ @Override
+ public void accept(byte[] message) {
+ HeartbeatMessage hb = SERIALIZER.decode(message);
+ failureDetector.report(hb.source().id());
+ hb.knownPeers().forEach(node -> {
+ allNodes.put(node.id(), node);
+ });
+ }
+ }
+
+ private static class HeartbeatMessage {
+ private ControllerNode source;
+ private Set<ControllerNode> knownPeers;
+
+ public HeartbeatMessage(ControllerNode source, Set<ControllerNode> members) {
+ this.source = source;
+ this.knownPeers = ImmutableSet.copyOf(members);
+ }
+
+ public ControllerNode source() {
+ return source;
+ }
+
+ public Set<ControllerNode> knownPeers() {
+ return knownPeers;
+ }
+ }
+
+ @Override
+ public DateTime getLastUpdated(NodeId nodeId) {
+ return nodeStateLastUpdatedTimes.get(nodeId);
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/NodeInfo.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/NodeInfo.java
new file mode 100644
index 00000000..d436ca76
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/NodeInfo.java
@@ -0,0 +1,118 @@
+/*
+ * 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.store.cluster.impl;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Objects;
+
+import org.onosproject.cluster.ControllerNode;
+
+/**
+ * Node info read from configuration files during bootstrap.
+ */
+public final class NodeInfo {
+ private final String id;
+ private final String ip;
+ private final int tcpPort;
+
+ private NodeInfo(String id, String ip, int port) {
+ this.id = id;
+ this.ip = ip;
+ this.tcpPort = port;
+ }
+
+ /*
+ * Needed for serialization.
+ */
+ private NodeInfo() {
+ id = null;
+ ip = null;
+ tcpPort = 0;
+ }
+
+ /**
+ * Creates a new instance.
+ * @param id node id
+ * @param ip node ip address
+ * @param port tcp port
+ * @return NodeInfo
+ */
+ public static NodeInfo from(String id, String ip, int port) {
+ NodeInfo node = new NodeInfo(id, ip, port);
+ return node;
+ }
+
+ /**
+ * Returns the NodeInfo for a controller node.
+ * @param node controller node
+ * @return NodeInfo
+ */
+ public static NodeInfo of(ControllerNode node) {
+ return NodeInfo.from(node.id().toString(), node.ip().toString(), node.tcpPort());
+ }
+
+ /**
+ * Returns node id.
+ * @return node id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns node ip.
+ * @return node ip
+ */
+ public String getIp() {
+ return ip;
+ }
+
+ /**
+ * Returns node port.
+ * @return port
+ */
+ public int getTcpPort() {
+ return tcpPort;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, ip, tcpPort);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof NodeInfo) {
+ NodeInfo that = (NodeInfo) o;
+ return Objects.equals(this.id, that.id) &&
+ Objects.equals(this.ip, that.ip) &&
+ Objects.equals(this.tcpPort, that.tcpPort);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("id", id)
+ .add("ip", ip)
+ .add("tcpPort", tcpPort).toString();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/PhiAccrualFailureDetector.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/PhiAccrualFailureDetector.java
new file mode 100644
index 00000000..cdb138b4
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/PhiAccrualFailureDetector.java
@@ -0,0 +1,119 @@
+/*
+ * 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.store.cluster.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Phi Accrual failure detector.
+ * <p>
+ * Based on a paper titled: "The φ Accrual Failure Detector" by Hayashibara, et al.
+ */
+public class PhiAccrualFailureDetector {
+ private final Map<NodeId, History> states = Maps.newConcurrentMap();
+
+ // TODO: make these configurable.
+ private static final int WINDOW_SIZE = 250;
+ private static final int MIN_SAMPLES = 25;
+ private static final double PHI_FACTOR = 1.0 / Math.log(10.0);
+
+ // If a node does not have any heartbeats, this is the phi
+ // value to report. Indicates the node is inactive (from the
+ // detectors perspective.
+ private static final double BOOTSTRAP_PHI_VALUE = 100.0;
+
+ /**
+ * Report a new heart beat for the specified node id.
+ * @param nodeId node id
+ */
+ public void report(NodeId nodeId) {
+ report(nodeId, System.currentTimeMillis());
+ }
+
+ /**
+ * Report a new heart beat for the specified node id.
+ * @param nodeId node id
+ * @param arrivalTime arrival time
+ */
+ public void report(NodeId nodeId, long arrivalTime) {
+ checkNotNull(nodeId, "NodeId must not be null");
+ checkArgument(arrivalTime >= 0, "arrivalTime must not be negative");
+ History nodeState =
+ states.computeIfAbsent(nodeId, key -> new History());
+ synchronized (nodeState) {
+ long latestHeartbeat = nodeState.latestHeartbeatTime();
+ if (latestHeartbeat != -1) {
+ nodeState.samples().addValue(arrivalTime - latestHeartbeat);
+ }
+ nodeState.setLatestHeartbeatTime(arrivalTime);
+ }
+ }
+
+ /**
+ * Compute phi for the specified node id.
+ * @param nodeId node id
+ * @return phi value
+ */
+ public double phi(NodeId nodeId) {
+ checkNotNull(nodeId, "NodeId must not be null");
+ if (!states.containsKey(nodeId)) {
+ return BOOTSTRAP_PHI_VALUE;
+ }
+ History nodeState = states.get(nodeId);
+ synchronized (nodeState) {
+ long latestHeartbeat = nodeState.latestHeartbeatTime();
+ DescriptiveStatistics samples = nodeState.samples();
+ if (latestHeartbeat == -1 || samples.getN() < MIN_SAMPLES) {
+ return 0.0;
+ }
+ return computePhi(samples, latestHeartbeat, System.currentTimeMillis());
+ }
+ }
+
+ private double computePhi(DescriptiveStatistics samples, long tLast, long tNow) {
+ long size = samples.getN();
+ long t = tNow - tLast;
+ return (size > 0)
+ ? PHI_FACTOR * t / samples.getMean()
+ : BOOTSTRAP_PHI_VALUE;
+ }
+
+ private static class History {
+ DescriptiveStatistics samples =
+ new DescriptiveStatistics(WINDOW_SIZE);
+ long lastHeartbeatTime = -1;
+
+ public DescriptiveStatistics samples() {
+ return samples;
+ }
+
+ public long latestHeartbeatTime() {
+ return lastHeartbeatTime;
+ }
+
+ public void setLatestHeartbeatTime(long value) {
+ lastHeartbeatTime = value;
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/package-info.java
new file mode 100644
index 00000000..9e2db676
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of a distributed cluster node store using Hazelcast.
+ */
+package org.onosproject.store.cluster.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManager.java
new file mode 100644
index 00000000..8a237ef0
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManager.java
@@ -0,0 +1,261 @@
+/*
+ * 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.store.cluster.messaging.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.util.Tools;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.Endpoint;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.cluster.messaging.MessagingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Component(immediate = true)
+@Service
+public class ClusterCommunicationManager
+ implements ClusterCommunicationService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MessagingService messagingService;
+
+ private NodeId localNodeId;
+
+ @Activate
+ public void activate() {
+ localNodeId = clusterService.getLocalNode().id();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public <M> void broadcast(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder) {
+ multicast(message,
+ subject,
+ encoder,
+ clusterService.getNodes()
+ .stream()
+ .filter(node -> !Objects.equal(node, clusterService.getLocalNode()))
+ .map(ControllerNode::id)
+ .collect(Collectors.toSet()));
+ }
+
+ @Override
+ public <M> void broadcastIncludeSelf(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder) {
+ multicast(message,
+ subject,
+ encoder,
+ clusterService.getNodes()
+ .stream()
+ .map(ControllerNode::id)
+ .collect(Collectors.toSet()));
+ }
+
+ @Override
+ public <M> CompletableFuture<Void> unicast(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder,
+ NodeId toNodeId) {
+ try {
+ byte[] payload = new ClusterMessage(
+ localNodeId,
+ subject,
+ encoder.apply(message)).getBytes();
+ return doUnicast(subject, payload, toNodeId);
+ } catch (Exception e) {
+ return Tools.exceptionalFuture(e);
+ }
+ }
+
+ @Override
+ public <M> void multicast(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder,
+ Set<NodeId> nodes) {
+ byte[] payload = new ClusterMessage(
+ localNodeId,
+ subject,
+ encoder.apply(message)).getBytes();
+ nodes.forEach(nodeId -> doUnicast(subject, payload, nodeId));
+ }
+
+ @Override
+ public <M, R> CompletableFuture<R> sendAndReceive(M message,
+ MessageSubject subject,
+ Function<M, byte[]> encoder,
+ Function<byte[], R> decoder,
+ NodeId toNodeId) {
+ try {
+ ClusterMessage envelope = new ClusterMessage(
+ clusterService.getLocalNode().id(),
+ subject,
+ encoder.apply(message));
+ return sendAndReceive(subject, envelope.getBytes(), toNodeId).thenApply(decoder);
+ } catch (Exception e) {
+ return Tools.exceptionalFuture(e);
+ }
+ }
+
+ private CompletableFuture<Void> doUnicast(MessageSubject subject, byte[] payload, NodeId toNodeId) {
+ ControllerNode node = clusterService.getNode(toNodeId);
+ checkArgument(node != null, "Unknown nodeId: %s", toNodeId);
+ Endpoint nodeEp = new Endpoint(node.ip(), node.tcpPort());
+ return messagingService.sendAsync(nodeEp, subject.value(), payload);
+ }
+
+ private CompletableFuture<byte[]> sendAndReceive(MessageSubject subject, byte[] payload, NodeId toNodeId) {
+ ControllerNode node = clusterService.getNode(toNodeId);
+ checkArgument(node != null, "Unknown nodeId: %s", toNodeId);
+ Endpoint nodeEp = new Endpoint(node.ip(), node.tcpPort());
+ return messagingService.sendAndReceive(nodeEp, subject.value(), payload);
+ }
+
+ @Override
+ public void addSubscriber(MessageSubject subject,
+ ClusterMessageHandler subscriber,
+ ExecutorService executor) {
+ messagingService.registerHandler(subject.value(),
+ new InternalClusterMessageHandler(subscriber),
+ executor);
+ }
+
+ @Override
+ public void removeSubscriber(MessageSubject subject) {
+ messagingService.unregisterHandler(subject.value());
+ }
+
+ @Override
+ public <M, R> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder,
+ Function<M, R> handler,
+ Function<R, byte[]> encoder,
+ Executor executor) {
+ messagingService.registerHandler(subject.value(),
+ new InternalMessageResponder<M, R>(decoder, encoder, m -> {
+ CompletableFuture<R> responseFuture = new CompletableFuture<>();
+ executor.execute(() -> {
+ try {
+ responseFuture.complete(handler.apply(m));
+ } catch (Exception e) {
+ responseFuture.completeExceptionally(e);
+ }
+ });
+ return responseFuture;
+ }));
+ }
+
+ @Override
+ public <M, R> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder,
+ Function<M, CompletableFuture<R>> handler,
+ Function<R, byte[]> encoder) {
+ messagingService.registerHandler(subject.value(),
+ new InternalMessageResponder<>(decoder, encoder, handler));
+ }
+
+ @Override
+ public <M> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder,
+ Consumer<M> handler,
+ Executor executor) {
+ messagingService.registerHandler(subject.value(),
+ new InternalMessageConsumer<>(decoder, handler),
+ executor);
+ }
+
+ private class InternalClusterMessageHandler implements Function<byte[], byte[]> {
+ private ClusterMessageHandler handler;
+
+ public InternalClusterMessageHandler(ClusterMessageHandler handler) {
+ this.handler = handler;
+ }
+
+ @Override
+ public byte[] apply(byte[] bytes) {
+ ClusterMessage message = ClusterMessage.fromBytes(bytes);
+ handler.handle(message);
+ return message.response();
+ }
+ }
+
+ private class InternalMessageResponder<M, R> implements Function<byte[], CompletableFuture<byte[]>> {
+ private final Function<byte[], M> decoder;
+ private final Function<R, byte[]> encoder;
+ private final Function<M, CompletableFuture<R>> handler;
+
+ public InternalMessageResponder(Function<byte[], M> decoder,
+ Function<R, byte[]> encoder,
+ Function<M, CompletableFuture<R>> handler) {
+ this.decoder = decoder;
+ this.encoder = encoder;
+ this.handler = handler;
+ }
+
+ @Override
+ public CompletableFuture<byte[]> apply(byte[] bytes) {
+ return handler.apply(decoder.apply(ClusterMessage.fromBytes(bytes).payload())).thenApply(encoder);
+ }
+ }
+
+ private class InternalMessageConsumer<M> implements Consumer<byte[]> {
+ private final Function<byte[], M> decoder;
+ private final Consumer<M> consumer;
+
+ public InternalMessageConsumer(Function<byte[], M> decoder, Consumer<M> consumer) {
+ this.decoder = decoder;
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void accept(byte[] bytes) {
+ consumer.accept(decoder.apply(ClusterMessage.fromBytes(bytes).payload()));
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java
new file mode 100644
index 00000000..9e52c3e3
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java
@@ -0,0 +1,40 @@
+package org.onosproject.store.cluster.messaging.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.nio.service.IOLoopMessaging;
+import org.onosproject.cluster.ClusterDefinitionService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.store.cluster.messaging.Endpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * IOLoop based MessagingService.
+ */
+@Component(immediate = true, enabled = false)
+@Service
+public class IOLoopMessagingManager extends IOLoopMessaging {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterDefinitionService clusterDefinitionService;
+
+ @Activate
+ public void activate() throws Exception {
+ ControllerNode localNode = clusterDefinitionService.localNode();
+ super.start(new Endpoint(localNode.ip(), localNode.tcpPort()));
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() throws Exception {
+ super.stop();
+ log.info("Stopped");
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java
new file mode 100644
index 00000000..8b2cc8e2
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java
@@ -0,0 +1,72 @@
+package org.onosproject.store.cluster.messaging.impl;
+
+import com.google.common.base.Strings;
+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.netty.NettyMessaging;
+import org.onosproject.cluster.ClusterDefinitionService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.store.cluster.messaging.Endpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Netty based MessagingService.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class NettyMessagingManager extends NettyMessaging {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final short MIN_KS_LENGTH = 6;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterDefinitionService clusterDefinitionService;
+
+ @Activate
+ public void activate() throws Exception {
+ ControllerNode localNode = clusterDefinitionService.localNode();
+ getTLSParameters();
+ super.start(new Endpoint(localNode.ip(), localNode.tcpPort()));
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() throws Exception {
+ super.stop();
+ log.info("Stopped");
+ }
+
+ private void getTLSParameters() {
+ String tempString = System.getProperty("enableNettyTLS");
+ enableNettyTLS = Strings.isNullOrEmpty(tempString) ? TLS_DISABLED : Boolean.parseBoolean(tempString);
+ log.info("enableNettyTLS = {}", enableNettyTLS);
+ if (enableNettyTLS) {
+ ksLocation = System.getProperty("javax.net.ssl.keyStore");
+ if (Strings.isNullOrEmpty(ksLocation)) {
+ enableNettyTLS = TLS_DISABLED;
+ return;
+ }
+ tsLocation = System.getProperty("javax.net.ssl.trustStore");
+ if (Strings.isNullOrEmpty(tsLocation)) {
+ enableNettyTLS = TLS_DISABLED;
+ return;
+ }
+ ksPwd = System.getProperty("javax.net.ssl.keyStorePassword").toCharArray();
+ if (MIN_KS_LENGTH > ksPwd.length) {
+ enableNettyTLS = TLS_DISABLED;
+ return;
+ }
+ tsPwd = System.getProperty("javax.net.ssl.trustStorePassword").toCharArray();
+ if (MIN_KS_LENGTH > tsPwd.length) {
+ enableNettyTLS = TLS_DISABLED;
+ return;
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/package-info.java
new file mode 100644
index 00000000..7157277e
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/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.
+ */
+
+/**
+ * Implementation of the cluster messaging mechanism.
+ */
+package org.onosproject.store.cluster.messaging.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
new file mode 100644
index 00000000..3e73d8f4
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
@@ -0,0 +1,289 @@
+/*
+ * 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.store.config.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.DoubleNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.LongNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.ShortNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+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.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigStore;
+import org.onosproject.net.config.NetworkConfigStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapException;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.onosproject.net.config.NetworkConfigEvent.Type.*;
+
+/**
+ * Implementation of a distributed network configuration store.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedNetworkConfigStore
+ extends AbstractStore<NetworkConfigEvent, NetworkConfigStoreDelegate>
+ implements NetworkConfigStore {
+
+ private static final int MAX_BACKOFF = 10;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private ConsistentMap<ConfigKey, JsonNode> configs;
+
+ private final Map<String, ConfigFactory> factoriesByConfig = Maps.newConcurrentMap();
+ private final ObjectMapper mapper = new ObjectMapper();
+ private final ConfigApplyDelegate applyDelegate = new InternalApplyDelegate();
+ private final MapEventListener<ConfigKey, JsonNode> listener = new InternalMapListener();
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
+ .register(KryoNamespaces.API)
+ .register(ConfigKey.class, ObjectNode.class, ArrayNode.class,
+ JsonNodeFactory.class, LinkedHashMap.class,
+ TextNode.class, BooleanNode.class,
+ LongNode.class, DoubleNode.class, ShortNode.class, IntNode.class);
+
+ configs = storageService.<ConfigKey, JsonNode>consistentMapBuilder()
+ .withSerializer(Serializer.using(kryoBuilder.build()))
+ .withName("onos-network-configs")
+ .withRelaxedReadConsistency()
+ .build();
+ configs.addListener(listener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ configs.removeListener(listener);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void addConfigFactory(ConfigFactory configFactory) {
+ factoriesByConfig.put(configFactory.configClass().getName(), configFactory);
+ notifyDelegate(new NetworkConfigEvent(CONFIG_REGISTERED, configFactory.configKey(),
+ configFactory.configClass()));
+ }
+
+ @Override
+ public void removeConfigFactory(ConfigFactory configFactory) {
+ factoriesByConfig.remove(configFactory.configClass().getName());
+ notifyDelegate(new NetworkConfigEvent(CONFIG_UNREGISTERED, configFactory.configKey(),
+ configFactory.configClass()));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) {
+ return (ConfigFactory<S, C>) factoriesByConfig.get(configClass.getName());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <S> Set<S> getSubjects(Class<S> subjectClass) {
+ ImmutableSet.Builder<S> builder = ImmutableSet.builder();
+ configs.keySet().forEach(k -> {
+ if (subjectClass.isInstance(k.subject)) {
+ builder.add((S) k.subject);
+ }
+ });
+ return builder.build();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
+ ImmutableSet.Builder<S> builder = ImmutableSet.builder();
+ String cName = configClass.getName();
+ configs.keySet().forEach(k -> {
+ if (subjectClass.isInstance(k.subject) && cName.equals(k.configClass)) {
+ builder.add((S) k.subject);
+ }
+ });
+ return builder.build();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <S> Set<Class<? extends Config<S>>> getConfigClasses(S subject) {
+ ImmutableSet.Builder<Class<? extends Config<S>>> builder = ImmutableSet.builder();
+ configs.keySet().forEach(k -> {
+ if (Objects.equals(subject, k.subject) && delegate != null) {
+ builder.add(factoriesByConfig.get(k.configClass).configClass());
+ }
+ });
+ return builder.build();
+ }
+
+ @Override
+ public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) {
+ // TODO: need to identify and address the root cause for timeouts.
+ Versioned<JsonNode> json = Tools.retryable(configs::get, ConsistentMapException.class, 1, MAX_BACKOFF)
+ .apply(key(subject, configClass));
+ return json != null ? createConfig(subject, configClass, json.value()) : null;
+ }
+
+
+ @Override
+ public <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass) {
+ ConfigFactory<S, C> factory = getConfigFactory(configClass);
+ Versioned<JsonNode> json = configs.computeIfAbsent(key(subject, configClass),
+ k -> factory.isList() ?
+ mapper.createArrayNode() :
+ mapper.createObjectNode());
+ return createConfig(subject, configClass, json.value());
+ }
+
+ @Override
+ public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
+ return createConfig(subject, configClass,
+ configs.putAndGet(key(subject, configClass), json).value());
+ }
+
+ @Override
+ public <S, C extends Config<S>> void clearConfig(S subject, Class<C> configClass) {
+ configs.remove(key(subject, configClass));
+ }
+
+ /**
+ * Produces a config from the specified subject, config class and raw JSON.
+ *
+ * @param subject config subject
+ * @param configClass config class
+ * @param json raw JSON data
+ * @return config object or null of no factory found or if the specified
+ * JSON is null
+ */
+ @SuppressWarnings("unchecked")
+ private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
+ JsonNode json) {
+ if (json != null) {
+ ConfigFactory<S, C> factory = factoriesByConfig.get(configClass.getName());
+ if (factory != null) {
+ C config = factory.createConfig();
+ config.init(subject, factory.configKey(), json, mapper, applyDelegate);
+ return config;
+ }
+ }
+ return null;
+ }
+
+
+ // Auxiliary delegate to receive notifications about changes applied to
+ // the network configuration - by the apps.
+ private class InternalApplyDelegate implements ConfigApplyDelegate {
+ @Override
+ public void onApply(Config config) {
+ configs.put(key(config.subject(), config.getClass()), config.node());
+ }
+ }
+
+ // Produces a key for uniquely tracking a subject config.
+ private static ConfigKey key(Object subject, Class<?> configClass) {
+ return new ConfigKey(subject, configClass);
+ }
+
+ // Auxiliary key to track subject configurations.
+ private static final class ConfigKey {
+ final Object subject;
+ final String configClass;
+
+ private ConfigKey(Object subject, Class<?> configClass) {
+ this.subject = subject;
+ this.configClass = configClass.getName();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subject, 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.subject, other.subject)
+ && Objects.equals(this.configClass, other.configClass);
+ }
+ return false;
+ }
+ }
+
+ private class InternalMapListener implements MapEventListener<ConfigKey, JsonNode> {
+ @Override
+ public void event(MapEvent<ConfigKey, JsonNode> event) {
+ NetworkConfigEvent.Type type;
+ switch (event.type()) {
+ case INSERT:
+ type = CONFIG_ADDED;
+ break;
+ case UPDATE:
+ type = CONFIG_UPDATED;
+ break;
+ case REMOVE:
+ default:
+ type = CONFIG_REMOVED;
+ break;
+ }
+ ConfigFactory factory = factoriesByConfig.get(event.key().configClass);
+ if (factory != null) {
+ notifyDelegate(new NetworkConfigEvent(type, event.key().subject,
+ factory.configClass()));
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/config/impl/package-info.java
new file mode 100644
index 00000000..0e1264eb
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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 distributed store.
+ */
+package org.onosproject.store.config.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/AsyncCachingConsistentMap.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/AsyncCachingConsistentMap.java
new file mode 100644
index 00000000..7e575b01
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/AsyncCachingConsistentMap.java
@@ -0,0 +1,71 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.Versioned;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+/**
+ * Extension of DefaultAsyncConsistentMap that provides a weaker read consistency
+ * guarantee in return for better read performance.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public class AsyncCachingConsistentMap<K, V> extends DefaultAsyncConsistentMap<K, V> {
+
+ private final LoadingCache<K, CompletableFuture<Versioned<V>>> cache =
+ CacheBuilder.newBuilder()
+ .maximumSize(10000) // TODO: make configurable
+ .build(new CacheLoader<K, CompletableFuture<Versioned<V>>>() {
+ @Override
+ public CompletableFuture<Versioned<V>> load(K key)
+ throws Exception {
+ return AsyncCachingConsistentMap.super.get(key);
+ }
+ });
+
+ public AsyncCachingConsistentMap(String name,
+ ApplicationId applicationId,
+ Database database,
+ Serializer serializer,
+ boolean readOnly,
+ boolean purgeOnUninstall,
+ boolean meteringEnabled) {
+ super(name, applicationId, database, serializer, readOnly, purgeOnUninstall, meteringEnabled);
+ addListener(event -> cache.invalidate(event.key()));
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> get(K key) {
+ CompletableFuture<Versioned<V>> cachedValue = cache.getIfPresent(key);
+ if (cachedValue != null) {
+ if (cachedValue.isCompletedExceptionally()) {
+ cache.invalidate(key);
+ } else {
+ return cachedValue;
+ }
+ }
+ return cache.getUnchecked(key);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CommitResponse.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CommitResponse.java
new file mode 100644
index 00000000..bbc8e6e0
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CommitResponse.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.store.consistent.impl;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Result of a Transaction commit operation.
+ */
+public final class CommitResponse {
+
+ private boolean success;
+ private List<UpdateResult<String, byte[]>> updates;
+
+ public static CommitResponse success(List<UpdateResult<String, byte[]>> updates) {
+ return new CommitResponse(true, updates);
+ }
+
+ public static CommitResponse failure() {
+ return new CommitResponse(false, Collections.emptyList());
+ }
+
+ private CommitResponse(boolean success, List<UpdateResult<String, byte[]>> updates) {
+ this.success = success;
+ this.updates = ImmutableList.copyOf(updates);
+ }
+
+ public boolean success() {
+ return success;
+ }
+
+ public List<UpdateResult<String, byte[]>> updates() {
+ return updates;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("success", success)
+ .add("udpates", updates)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/ConsistentMapBackedJavaMap.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/ConsistentMapBackedJavaMap.java
new file mode 100644
index 00000000..58aca31a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/ConsistentMapBackedJavaMap.java
@@ -0,0 +1,145 @@
+package org.onosproject.store.consistent.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Versioned;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Maps;
+
+/**
+ * Standard java Map backed by a ConsistentMap.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public final class ConsistentMapBackedJavaMap<K, V> implements Map<K, V> {
+
+ private final ConsistentMap<K, V> backingMap;
+
+ public ConsistentMapBackedJavaMap(ConsistentMap<K, V> backingMap) {
+ this.backingMap = backingMap;
+ }
+
+ @Override
+ public int size() {
+ return backingMap.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backingMap.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return backingMap.containsKey((K) key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return backingMap.containsValue((V) value);
+ }
+
+ @Override
+ public V get(Object key) {
+ return Versioned.valueOrElse(backingMap.get((K) key), null);
+ }
+
+ @Override
+ public V getOrDefault(Object key, V defaultValue) {
+ return Versioned.valueOrElse(backingMap.get((K) key), defaultValue);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ return Versioned.valueOrElse(backingMap.put(key, value), null);
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ return Versioned.valueOrElse(backingMap.putIfAbsent(key, value), null);
+ }
+
+ @Override
+ public V remove(Object key) {
+ return Versioned.valueOrElse(backingMap.remove((K) key), null);
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ return backingMap.remove((K) key, (V) value);
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return backingMap.replace(key, oldValue, newValue);
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ m.forEach((k, v) -> {
+ backingMap.put(k, v);
+ });
+ }
+
+ @Override
+ public void clear() {
+ backingMap.clear();
+ }
+
+ @Override
+ public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return Versioned.valueOrElse(backingMap.compute(key, remappingFunction), null);
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ return Versioned.valueOrElse(backingMap.computeIfAbsent(key, mappingFunction), null);
+ }
+
+ @Override
+ public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return Versioned.valueOrElse(backingMap.computeIfPresent(key, remappingFunction), null);
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return backingMap.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ return Collections2.transform(backingMap.values(), v -> v.value());
+ }
+
+ @Override
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ return backingMap.entrySet()
+ .stream()
+ .map(entry -> Maps.immutableEntry(entry.getKey(), entry.getValue().value()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ entrySet().forEach(e -> action.accept(e.getKey(), e.getValue()));
+ }
+
+ @Override
+ public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ return computeIfPresent(key, (k, v) -> v == null ? value : remappingFunction.apply(v, value));
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CopycatCommunicationProtocol.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CopycatCommunicationProtocol.java
new file mode 100644
index 00000000..88ddae62
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/CopycatCommunicationProtocol.java
@@ -0,0 +1,134 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+
+import org.onlab.util.Tools;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+import net.kuujo.copycat.protocol.AbstractProtocol;
+import net.kuujo.copycat.protocol.ProtocolClient;
+import net.kuujo.copycat.protocol.ProtocolHandler;
+import net.kuujo.copycat.protocol.ProtocolServer;
+import net.kuujo.copycat.util.Configurable;
+
+/**
+ * Protocol for Copycat communication that employs
+ * {@code ClusterCommunicationService}.
+ */
+public class CopycatCommunicationProtocol extends AbstractProtocol {
+
+ private static final MessageSubject COPYCAT_MESSAGE_SUBJECT =
+ new MessageSubject("onos-copycat-message");
+
+ protected ClusterService clusterService;
+ protected ClusterCommunicationService clusterCommunicator;
+
+ public CopycatCommunicationProtocol(ClusterService clusterService,
+ ClusterCommunicationService clusterCommunicator) {
+ this.clusterService = clusterService;
+ this.clusterCommunicator = clusterCommunicator;
+ }
+
+ @Override
+ public Configurable copy() {
+ return this;
+ }
+
+ @Override
+ public ProtocolClient createClient(URI uri) {
+ NodeId nodeId = uriToNodeId(uri);
+ if (nodeId == null) {
+ throw new IllegalStateException("Unknown peer " + uri);
+ }
+ return new Client(nodeId);
+ }
+
+ @Override
+ public ProtocolServer createServer(URI uri) {
+ return new Server();
+ }
+
+ private class Server implements ProtocolServer {
+
+ @Override
+ public void handler(ProtocolHandler handler) {
+ if (handler == null) {
+ clusterCommunicator.removeSubscriber(COPYCAT_MESSAGE_SUBJECT);
+ } else {
+ clusterCommunicator.addSubscriber(COPYCAT_MESSAGE_SUBJECT,
+ ByteBuffer::wrap,
+ handler,
+ Tools::byteBuffertoArray);
+ // FIXME: Tools::byteBuffertoArray involves a array copy.
+ }
+ }
+
+ @Override
+ public CompletableFuture<Void> listen() {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture<Void> close() {
+ clusterCommunicator.removeSubscriber(COPYCAT_MESSAGE_SUBJECT);
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ private class Client implements ProtocolClient {
+ private final NodeId peer;
+
+ public Client(NodeId peer) {
+ this.peer = peer;
+ }
+
+ @Override
+ public CompletableFuture<ByteBuffer> write(ByteBuffer request) {
+ return clusterCommunicator.sendAndReceive(request,
+ COPYCAT_MESSAGE_SUBJECT,
+ Tools::byteBuffertoArray,
+ ByteBuffer::wrap,
+ peer);
+ }
+
+ @Override
+ public CompletableFuture<Void> connect() {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture<Void> close() {
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ private NodeId uriToNodeId(URI uri) {
+ return clusterService.getNodes()
+ .stream()
+ .filter(node -> uri.getHost().equals(node.ip().toString()))
+ .map(ControllerNode::id)
+ .findAny()
+ .orElse(null);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Database.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Database.java
new file mode 100644
index 00000000..ff3e36ac
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Database.java
@@ -0,0 +1,106 @@
+/*
+ * 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.store.consistent.impl;
+
+
+import java.util.function.Consumer;
+
+import net.kuujo.copycat.cluster.ClusterConfig;
+import net.kuujo.copycat.cluster.internal.coordinator.ClusterCoordinator;
+import net.kuujo.copycat.cluster.internal.coordinator.CoordinatorConfig;
+import net.kuujo.copycat.cluster.internal.coordinator.DefaultClusterCoordinator;
+import net.kuujo.copycat.resource.Resource;
+
+/**
+ * Database.
+ */
+public interface Database extends DatabaseProxy<String, byte[]>, Resource<Database> {
+
+ /**
+ * Creates a new database with the default cluster configuration.<p>
+ *
+ * The database will be constructed with the default cluster configuration. The default cluster configuration
+ * searches for two resources on the classpath - {@code cluster} and {cluster-defaults} - in that order. Configuration
+ * options specified in {@code cluster.conf} will override those in {cluster-defaults.conf}.<p>
+ *
+ * Additionally, the database will be constructed with an database configuration that searches the classpath for
+ * three configuration files - {@code {name}}, {@code database}, {@code database-defaults}, {@code resource}, and
+ * {@code resource-defaults} - in that order. The first resource is a configuration resource with the same name
+ * as the map resource. If the resource is namespaced - e.g. `databases.my-database.conf` - then resource
+ * configurations will be loaded according to namespaces as well; for example, `databases.conf`.
+ *
+ * @param name The database name.
+ * @return The database.
+ */
+ static Database create(String name) {
+ return create(name, new ClusterConfig(), new DatabaseConfig());
+ }
+
+ /**
+ * Creates a new database.<p>
+ *
+ * The database will be constructed with an database configuration that searches the classpath for
+ * three configuration files - {@code {name}}, {@code database}, {@code database-defaults}, {@code resource}, and
+ * {@code resource-defaults} - in that order. The first resource is a configuration resource with the same name
+ * as the database resource. If the resource is namespaced - e.g. `databases.my-database.conf` - then resource
+ * configurations will be loaded according to namespaces as well; for example, `databases.conf`.
+ *
+ * @param name The database name.
+ * @param cluster The cluster configuration.
+ * @return The database.
+ */
+ static Database create(String name, ClusterConfig cluster) {
+ return create(name, cluster, new DatabaseConfig());
+ }
+
+ /**
+ * Creates a new database.
+ *
+ * @param name The database name.
+ * @param cluster The cluster configuration.
+ * @param config The database configuration.
+
+ * @return The database.
+ */
+ static Database create(String name, ClusterConfig cluster, DatabaseConfig config) {
+ ClusterCoordinator coordinator =
+ new DefaultClusterCoordinator(new CoordinatorConfig().withName(name).withClusterConfig(cluster));
+ return coordinator.<Database>getResource(name, config.resolve(cluster))
+ .addStartupTask(() -> coordinator.open().thenApply(v -> null))
+ .addShutdownTask(coordinator::close);
+ }
+
+ /**
+ * Tells whether the database supports change notifications.
+ * @return true if notifications are supported; false otherwise
+ */
+ default boolean hasChangeNotificationSupport() {
+ return true;
+ }
+
+ /**
+ * Registers a new consumer of StateMachineUpdates.
+ * @param consumer consumer to register
+ */
+ void registerConsumer(Consumer<StateMachineUpdate> consumer);
+
+ /**
+ * Unregisters a consumer of StateMachineUpdates.
+ * @param consumer consumer to unregister
+ */
+ void unregisterConsumer(Consumer<StateMachineUpdate> consumer);
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseConfig.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseConfig.java
new file mode 100644
index 00000000..bd774b99
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseConfig.java
@@ -0,0 +1,157 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.typesafe.config.ConfigValueFactory;
+import net.kuujo.copycat.cluster.ClusterConfig;
+import net.kuujo.copycat.cluster.internal.coordinator.CoordinatedResourceConfig;
+import net.kuujo.copycat.protocol.Consistency;
+import net.kuujo.copycat.resource.ResourceConfig;
+import net.kuujo.copycat.state.StateLogConfig;
+import net.kuujo.copycat.util.internal.Assert;
+
+import java.util.Map;
+
+/**
+ * Database configuration.
+ *
+ */
+public class DatabaseConfig extends ResourceConfig<DatabaseConfig> {
+ private static final String DATABASE_CONSISTENCY = "consistency";
+
+ private static final String DEFAULT_CONFIGURATION = "database-defaults";
+ private static final String CONFIGURATION = "database";
+
+ private String name;
+
+ public DatabaseConfig() {
+ super(CONFIGURATION, DEFAULT_CONFIGURATION);
+ }
+
+ public DatabaseConfig(Map<String, Object> config) {
+ super(config, CONFIGURATION, DEFAULT_CONFIGURATION);
+ }
+
+ public DatabaseConfig(String resource) {
+ super(resource, CONFIGURATION, DEFAULT_CONFIGURATION);
+ }
+
+ protected DatabaseConfig(DatabaseConfig config) {
+ super(config);
+ }
+
+ @Override
+ public DatabaseConfig copy() {
+ return new DatabaseConfig(this);
+ }
+
+ /**
+ * Sets the database read consistency.
+ *
+ * @param consistency The database read consistency.
+ * @throws java.lang.NullPointerException If the consistency is {@code null}
+ */
+ public void setConsistency(String consistency) {
+ this.config = config.withValue(DATABASE_CONSISTENCY,
+ ConfigValueFactory.fromAnyRef(
+ Consistency.parse(Assert.isNotNull(consistency, "consistency")).toString()));
+ }
+
+ /**
+ * Sets the database read consistency.
+ *
+ * @param consistency The database read consistency.
+ * @throws java.lang.NullPointerException If the consistency is {@code null}
+ */
+ public void setConsistency(Consistency consistency) {
+ this.config = config.withValue(DATABASE_CONSISTENCY,
+ ConfigValueFactory.fromAnyRef(
+ Assert.isNotNull(consistency, "consistency").toString()));
+ }
+
+ /**
+ * Returns the database read consistency.
+ *
+ * @return The database read consistency.
+ */
+ public Consistency getConsistency() {
+ return Consistency.parse(config.getString(DATABASE_CONSISTENCY));
+ }
+
+ /**
+ * Sets the database read consistency, returning the configuration for method chaining.
+ *
+ * @param consistency The database read consistency.
+ * @return The database configuration.
+ * @throws java.lang.NullPointerException If the consistency is {@code null}
+ */
+ public DatabaseConfig withConsistency(String consistency) {
+ setConsistency(consistency);
+ return this;
+ }
+
+ /**
+ * Sets the database read consistency, returning the configuration for method chaining.
+ *
+ * @param consistency The database read consistency.
+ * @return The database configuration.
+ * @throws java.lang.NullPointerException If the consistency is {@code null}
+ */
+ public DatabaseConfig withConsistency(Consistency consistency) {
+ setConsistency(consistency);
+ return this;
+ }
+
+ /**
+ * Returns the database name.
+ *
+ * @return The database name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the database name, returning the configuration for method chaining.
+ *
+ * @param name The database name
+ * @return The database configuration
+ * @throws java.lang.NullPointerException If the name is {@code null}
+ */
+ public DatabaseConfig withName(String name) {
+ setName(Assert.isNotNull(name, "name"));
+ return this;
+ }
+
+ /**
+ * Sets the database name.
+ *
+ * @param name The database name
+ * @throws java.lang.NullPointerException If the name is {@code null}
+ */
+ public void setName(String name) {
+ this.name = Assert.isNotNull(name, "name");
+ }
+
+ @Override
+ public CoordinatedResourceConfig resolve(ClusterConfig cluster) {
+ return new StateLogConfig(toMap())
+ .resolve(cluster)
+ .withResourceType(DefaultDatabase.class);
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java
new file mode 100644
index 00000000..11b56c14
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java
@@ -0,0 +1,108 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.onosproject.store.cluster.impl.NodeInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Partitioned database configuration.
+ */
+public class DatabaseDefinition {
+ private Map<String, Set<NodeInfo>> partitions;
+ private Set<NodeInfo> nodes;
+
+ /**
+ * Creates a new DatabaseDefinition.
+ *
+ * @param partitions partition map
+ * @param nodes set of nodes
+ * @return database definition
+ */
+ public static DatabaseDefinition from(Map<String, Set<NodeInfo>> partitions,
+ Set<NodeInfo> nodes) {
+ checkNotNull(partitions);
+ checkNotNull(nodes);
+ DatabaseDefinition definition = new DatabaseDefinition();
+ definition.partitions = ImmutableMap.copyOf(partitions);
+ definition.nodes = ImmutableSet.copyOf(nodes);
+ return definition;
+ }
+
+ /**
+ * Creates a new DatabaseDefinition using default partitions.
+ *
+ * @param nodes set of nodes
+ * @return database definition
+ */
+ public static DatabaseDefinition from(Set<NodeInfo> nodes) {
+ return from(generateDefaultPartitions(nodes), nodes);
+ }
+
+ /**
+ * Returns the map of database partitions.
+ *
+ * @return db partition map
+ */
+ public Map<String, Set<NodeInfo>> getPartitions() {
+ return partitions;
+ }
+
+ /**
+ * Returns the set of nodes.
+ *
+ * @return nodes
+ */
+ public Set<NodeInfo> getNodes() {
+ return nodes;
+ }
+
+
+ /**
+ * Generates set of default partitions using permutations of the nodes.
+ *
+ * @param nodes information about cluster nodes
+ * @return default partition map
+ */
+ private static Map<String, Set<NodeInfo>> generateDefaultPartitions(Set<NodeInfo> nodes) {
+ List<NodeInfo> sorted = new ArrayList<>(nodes);
+ Collections.sort(sorted, (o1, o2) -> o1.getId().compareTo(o2.getId()));
+ Map<String, Set<NodeInfo>> partitions = Maps.newHashMap();
+
+ int length = nodes.size();
+ int count = 3;
+ for (int i = 0; i < length; i++) {
+ Set<NodeInfo> set = new HashSet<>(count);
+ for (int j = 0; j < count; j++) {
+ set.add(sorted.get((i + j) % length));
+ }
+ partitions.put("p" + (i + 1), set);
+ }
+ return partitions;
+ }
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java
new file mode 100644
index 00000000..b77667b2
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java
@@ -0,0 +1,74 @@
+/*
+ * 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.store.consistent.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import java.io.File;
+import java.io.IOException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Files;
+
+/**
+ * Allows for reading and writing partitioned database definition as a JSON file.
+ */
+public class DatabaseDefinitionStore {
+
+ private final File file;
+
+ /**
+ * Creates a reader/writer of the database definition file.
+ *
+ * @param filePath location of the definition file
+ */
+ public DatabaseDefinitionStore(String filePath) {
+ file = new File(checkNotNull(filePath));
+ }
+
+ /**
+ * Creates a reader/writer of the database definition file.
+ *
+ * @param filePath location of the definition file
+ */
+ public DatabaseDefinitionStore(File filePath) {
+ file = checkNotNull(filePath);
+ }
+
+ /**
+ * Returns the database definition.
+ *
+ * @return database definition
+ * @throws IOException when I/O exception of some sort has occurred.
+ */
+ public DatabaseDefinition read() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(file, DatabaseDefinition.class);
+ }
+
+ /**
+ * Writes the specified database definition to file.
+ *
+ * @param definition database definition
+ * @throws IOException when I/O exception of some sort has occurred.
+ */
+ public void write(DatabaseDefinition definition) throws IOException {
+ checkNotNull(definition);
+ // write back to file
+ Files.createParentDirs(file);
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(file, definition);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java
new file mode 100644
index 00000000..b7c3794b
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java
@@ -0,0 +1,455 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+
+import net.kuujo.copycat.CopycatConfig;
+import net.kuujo.copycat.cluster.ClusterConfig;
+import net.kuujo.copycat.cluster.Member;
+import net.kuujo.copycat.cluster.Member.Type;
+import net.kuujo.copycat.cluster.internal.coordinator.ClusterCoordinator;
+import net.kuujo.copycat.cluster.internal.coordinator.DefaultClusterCoordinator;
+import net.kuujo.copycat.log.BufferedLog;
+import net.kuujo.copycat.log.FileLog;
+import net.kuujo.copycat.log.Log;
+import net.kuujo.copycat.protocol.Consistency;
+import net.kuujo.copycat.protocol.Protocol;
+import net.kuujo.copycat.util.concurrent.NamedThreadFactory;
+
+import org.apache.commons.lang.math.RandomUtils;
+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.app.ApplicationEvent;
+import org.onosproject.app.ApplicationListener;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.store.cluster.impl.ClusterDefinitionManager;
+import org.onosproject.store.cluster.impl.NodeInfo;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.ecmap.EventuallyConsistentMapBuilderImpl;
+import org.onosproject.store.service.AtomicCounterBuilder;
+import org.onosproject.store.service.AtomicValueBuilder;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.ConsistentMapException;
+import org.onosproject.store.service.DistributedQueueBuilder;
+import org.onosproject.store.service.EventuallyConsistentMapBuilder;
+import org.onosproject.store.service.MapInfo;
+import org.onosproject.store.service.PartitionInfo;
+import org.onosproject.store.service.DistributedSetBuilder;
+import org.onosproject.store.service.StorageAdminService;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.TransactionContextBuilder;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.app.ApplicationEvent.Type.APP_UNINSTALLED;
+import static org.onosproject.app.ApplicationEvent.Type.APP_DEACTIVATED;
+
+/**
+ * Database manager.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class DatabaseManager implements StorageService, StorageAdminService {
+
+ private final Logger log = getLogger(getClass());
+
+ public static final int COPYCAT_TCP_PORT = 9876;
+ public static final String PARTITION_DEFINITION_FILE = "../config/tablets.json";
+ public static final String BASE_PARTITION_NAME = "p0";
+
+ private static final int RAFT_ELECTION_TIMEOUT_MILLIS = 3000;
+ private static final int DATABASE_OPERATION_TIMEOUT_MILLIS = 5000;
+
+ private ClusterCoordinator coordinator;
+ protected PartitionedDatabase partitionedDatabase;
+ protected Database inMemoryDatabase;
+ protected NodeId localNodeId;
+
+ private TransactionManager transactionManager;
+ private final IdGenerator transactionIdGenerator = () -> RandomUtils.nextLong();
+
+ private ApplicationListener appListener = new InternalApplicationListener();
+
+ private final Multimap<String, DefaultAsyncConsistentMap> maps =
+ Multimaps.synchronizedMultimap(ArrayListMultimap.create());
+ private final Multimap<ApplicationId, DefaultAsyncConsistentMap> mapsByApplication =
+ Multimaps.synchronizedMultimap(ArrayListMultimap.create());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC)
+ protected ApplicationService applicationService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ protected String nodeToUri(NodeInfo node) {
+ return String.format("onos://%s:%d", node.getIp(), node.getTcpPort());
+ }
+
+ protected void bindApplicationService(ApplicationService service) {
+ applicationService = service;
+ applicationService.addListener(appListener);
+ }
+
+ protected void unbindApplicationService(ApplicationService service) {
+ applicationService.removeListener(appListener);
+ this.applicationService = null;
+ }
+
+ @Activate
+ public void activate() {
+ localNodeId = clusterService.getLocalNode().id();
+ // load database configuration
+ File databaseDefFile = new File(PARTITION_DEFINITION_FILE);
+ log.info("Loading database definition: {}", databaseDefFile.getAbsolutePath());
+
+ Map<String, Set<NodeInfo>> partitionMap;
+ try {
+ DatabaseDefinitionStore databaseDefStore = new DatabaseDefinitionStore(databaseDefFile);
+ if (!databaseDefFile.exists()) {
+ createDefaultDatabaseDefinition(databaseDefStore);
+ }
+ partitionMap = databaseDefStore.read().getPartitions();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to load database config", e);
+ }
+
+ String[] activeNodeUris = partitionMap.values()
+ .stream()
+ .reduce((s1, s2) -> Sets.union(s1, s2))
+ .get()
+ .stream()
+ .map(this::nodeToUri)
+ .toArray(String[]::new);
+
+ String localNodeUri = nodeToUri(NodeInfo.of(clusterService.getLocalNode()));
+ Protocol protocol = new CopycatCommunicationProtocol(clusterService, clusterCommunicator);
+
+ ClusterConfig clusterConfig = new ClusterConfig()
+ .withProtocol(protocol)
+ .withElectionTimeout(electionTimeoutMillis(activeNodeUris))
+ .withHeartbeatInterval(heartbeatTimeoutMillis(activeNodeUris))
+ .withMembers(activeNodeUris)
+ .withLocalMember(localNodeUri);
+
+ CopycatConfig copycatConfig = new CopycatConfig()
+ .withName("onos")
+ .withClusterConfig(clusterConfig)
+ .withDefaultSerializer(new DatabaseSerializer())
+ .withDefaultExecutor(Executors.newSingleThreadExecutor(new NamedThreadFactory("copycat-coordinator-%d")));
+
+ coordinator = new DefaultClusterCoordinator(copycatConfig.resolve());
+
+ DatabaseConfig inMemoryDatabaseConfig =
+ newDatabaseConfig(BASE_PARTITION_NAME, newInMemoryLog(), activeNodeUris);
+ inMemoryDatabase = coordinator
+ .getResource(inMemoryDatabaseConfig.getName(), inMemoryDatabaseConfig.resolve(clusterConfig)
+ .withSerializer(copycatConfig.getDefaultSerializer())
+ .withDefaultExecutor(copycatConfig.getDefaultExecutor()));
+
+ List<Database> partitions = partitionMap.entrySet()
+ .stream()
+ .map(entry -> {
+ String[] replicas = entry.getValue().stream().map(this::nodeToUri).toArray(String[]::new);
+ return newDatabaseConfig(entry.getKey(), newPersistentLog(), replicas);
+ })
+ .map(config -> {
+ Database db = coordinator.getResource(config.getName(), config.resolve(clusterConfig)
+ .withSerializer(copycatConfig.getDefaultSerializer())
+ .withDefaultExecutor(copycatConfig.getDefaultExecutor()));
+ return db;
+ })
+ .collect(Collectors.toList());
+
+ partitionedDatabase = new PartitionedDatabase("onos-store", partitions);
+
+ CompletableFuture<Void> status = coordinator.open()
+ .thenCompose(v -> CompletableFuture.allOf(inMemoryDatabase.open(), partitionedDatabase.open())
+ .whenComplete((db, error) -> {
+ if (error != null) {
+ log.error("Failed to initialize database.", error);
+ } else {
+ log.info("Successfully initialized database.");
+ }
+ }));
+
+ Futures.getUnchecked(status);
+
+ transactionManager = new TransactionManager(partitionedDatabase, consistentMapBuilder());
+ partitionedDatabase.setTransactionManager(transactionManager);
+
+ log.info("Started");
+ }
+
+ private void createDefaultDatabaseDefinition(DatabaseDefinitionStore store) {
+ // Assumes IPv4 is returned.
+ String ip = ClusterDefinitionManager.getSiteLocalAddress();
+ NodeInfo node = NodeInfo.from(ip, ip, COPYCAT_TCP_PORT);
+ try {
+ store.write(DatabaseDefinition.from(ImmutableSet.of(node)));
+ } catch (IOException e) {
+ log.warn("Unable to write default cluster definition", e);
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ CompletableFuture.allOf(inMemoryDatabase.close(), partitionedDatabase.close())
+ .thenCompose(v -> coordinator.close())
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ log.warn("Failed to cleanly close databases.", error);
+ } else {
+ log.info("Successfully closed databases.");
+ }
+ });
+ maps.values().forEach(this::unregisterMap);
+ if (applicationService != null) {
+ applicationService.removeListener(appListener);
+ }
+ log.info("Stopped");
+ }
+
+ @Override
+ public TransactionContextBuilder transactionContextBuilder() {
+ return new DefaultTransactionContextBuilder(this, transactionIdGenerator.getNewId());
+ }
+
+ @Override
+ public List<PartitionInfo> getPartitionInfo() {
+ return Lists.asList(
+ inMemoryDatabase,
+ partitionedDatabase.getPartitions().toArray(new Database[]{}))
+ .stream()
+ .map(DatabaseManager::toPartitionInfo)
+ .collect(Collectors.toList());
+ }
+
+ private Log newPersistentLog() {
+ String logDir = System.getProperty("karaf.data", "./data");
+ return new FileLog()
+ .withDirectory(logDir)
+ .withSegmentSize(1073741824) // 1GB
+ .withFlushOnWrite(true)
+ .withSegmentInterval(Long.MAX_VALUE);
+ }
+
+ private Log newInMemoryLog() {
+ return new BufferedLog()
+ .withFlushOnWrite(false)
+ .withFlushInterval(Long.MAX_VALUE)
+ .withSegmentSize(10485760) // 10MB
+ .withSegmentInterval(Long.MAX_VALUE);
+ }
+
+ private DatabaseConfig newDatabaseConfig(String name, Log log, String[] replicas) {
+ return new DatabaseConfig()
+ .withName(name)
+ .withElectionTimeout(electionTimeoutMillis(replicas))
+ .withHeartbeatInterval(heartbeatTimeoutMillis(replicas))
+ .withConsistency(Consistency.DEFAULT)
+ .withLog(log)
+ .withDefaultSerializer(new DatabaseSerializer())
+ .withReplicas(replicas);
+ }
+
+ private long electionTimeoutMillis(String[] replicas) {
+ return replicas.length == 1 ? 10L : RAFT_ELECTION_TIMEOUT_MILLIS;
+ }
+
+ private long heartbeatTimeoutMillis(String[] replicas) {
+ return electionTimeoutMillis(replicas) / 2;
+ }
+
+ /**
+ * Maps a Raft Database object to a PartitionInfo object.
+ *
+ * @param database database containing input data
+ * @return PartitionInfo object
+ */
+ private static PartitionInfo toPartitionInfo(Database database) {
+ return new PartitionInfo(database.name(),
+ database.cluster().term(),
+ database.cluster().members()
+ .stream()
+ .filter(member -> Type.ACTIVE.equals(member.type()))
+ .map(Member::uri)
+ .sorted()
+ .collect(Collectors.toList()),
+ database.cluster().leader() != null ?
+ database.cluster().leader().uri() : null);
+ }
+
+
+ @Override
+ public <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder() {
+ return new EventuallyConsistentMapBuilderImpl<>(clusterService,
+ clusterCommunicator);
+ }
+
+ @Override
+ public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
+ return new DefaultConsistentMapBuilder<>(this);
+ }
+
+ @Override
+ public <E> DistributedSetBuilder<E> setBuilder() {
+ return new DefaultDistributedSetBuilder<>(this);
+ }
+
+
+ @Override
+ public <E> DistributedQueueBuilder<E> queueBuilder() {
+ return new DefaultDistributedQueueBuilder<>(this);
+ }
+
+ @Override
+ public AtomicCounterBuilder atomicCounterBuilder() {
+ return new DefaultAtomicCounterBuilder(inMemoryDatabase, partitionedDatabase);
+ }
+
+ @Override
+ public <V> AtomicValueBuilder<V> atomicValueBuilder() {
+ return new DefaultAtomicValueBuilder<>(this);
+ }
+
+ @Override
+ public List<MapInfo> getMapInfo() {
+ List<MapInfo> maps = Lists.newArrayList();
+ maps.addAll(getMapInfo(inMemoryDatabase));
+ maps.addAll(getMapInfo(partitionedDatabase));
+ return maps;
+ }
+
+ private List<MapInfo> getMapInfo(Database database) {
+ return complete(database.maps())
+ .stream()
+ .map(name -> new MapInfo(name, complete(database.mapSize(name))))
+ .filter(info -> info.size() > 0)
+ .collect(Collectors.toList());
+ }
+
+
+ @Override
+ public Map<String, Long> getCounters() {
+ Map<String, Long> counters = Maps.newHashMap();
+ counters.putAll(complete(inMemoryDatabase.counters()));
+ counters.putAll(complete(partitionedDatabase.counters()));
+ return counters;
+ }
+
+ @Override
+ public Map<String, Long> getPartitionedDatabaseCounters() {
+ Map<String, Long> counters = Maps.newHashMap();
+ counters.putAll(complete(partitionedDatabase.counters()));
+ return counters;
+ }
+
+ @Override
+ public Map<String, Long> getInMemoryDatabaseCounters() {
+ Map<String, Long> counters = Maps.newHashMap();
+ counters.putAll(complete(inMemoryDatabase.counters()));
+ return counters;
+ }
+
+ @Override
+ public Collection<Transaction> getTransactions() {
+ return complete(transactionManager.getTransactions());
+ }
+
+ private static <T> T complete(CompletableFuture<T> future) {
+ try {
+ return future.get(DATABASE_OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new ConsistentMapException.Interrupted();
+ } catch (TimeoutException e) {
+ throw new ConsistentMapException.Timeout();
+ } catch (ExecutionException e) {
+ throw new ConsistentMapException(e.getCause());
+ }
+ }
+
+ @Override
+ public void redriveTransactions() {
+ getTransactions().stream().forEach(transactionManager::execute);
+ }
+
+ protected <K, V> DefaultAsyncConsistentMap<K, V> registerMap(DefaultAsyncConsistentMap<K, V> map) {
+ maps.put(map.name(), map);
+ if (map.applicationId() != null) {
+ mapsByApplication.put(map.applicationId(), map);
+ }
+ return map;
+ }
+
+ protected <K, V> void unregisterMap(DefaultAsyncConsistentMap<K, V> map) {
+ maps.remove(map.name(), map);
+ if (map.applicationId() != null) {
+ mapsByApplication.remove(map.applicationId(), map);
+ }
+ }
+
+ private class InternalApplicationListener implements ApplicationListener {
+ @Override
+ public void event(ApplicationEvent event) {
+ if (event.type() == APP_UNINSTALLED || event.type() == APP_DEACTIVATED) {
+ ApplicationId appId = event.subject().id();
+ List<DefaultAsyncConsistentMap> mapsToRemove = ImmutableList.copyOf(mapsByApplication.get(appId));
+ mapsToRemove.forEach(DatabaseManager.this::unregisterMap);
+ if (event.type() == APP_UNINSTALLED) {
+ mapsToRemove.stream().filter(map -> map.purgeOnUninstall()).forEach(map -> map.clear());
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabasePartitioner.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabasePartitioner.java
new file mode 100644
index 00000000..740f81ad
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabasePartitioner.java
@@ -0,0 +1,45 @@
+/*
+ * 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.store.consistent.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.List;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.hash.Hashing;
+
+/**
+ * Partitioner for mapping map entries to individual database partitions.
+ * <p>
+ * By default a md5 hash of the hash key (key or map name) is used to pick a
+ * partition.
+ */
+public abstract class DatabasePartitioner implements Partitioner<String> {
+ // Database partitions sorted by their partition name.
+ protected final List<Database> partitions;
+
+ public DatabasePartitioner(List<Database> partitions) {
+ checkState(partitions != null && !partitions.isEmpty(), "Partitions cannot be null or empty");
+ this.partitions = ImmutableList.copyOf(partitions);
+ }
+
+ protected int hash(String key) {
+ return Math.abs(Hashing.md5().newHasher().putBytes(key.getBytes(Charsets.UTF_8)).hash().asInt());
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseProxy.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseProxy.java
new file mode 100644
index 00000000..95f9e39a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseProxy.java
@@ -0,0 +1,224 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+
+/**
+ * Database proxy.
+ */
+public interface DatabaseProxy<K, V> {
+
+ /**
+ * Returns a set of all map names.
+ *
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Set<String>> maps();
+
+ /**
+ * Returns a mapping from counter name to next value.
+ *
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Map<String, Long>> counters();
+
+ /**
+ * Returns the number of entries in map.
+ * @param mapName map name
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Integer> mapSize(String mapName);
+
+ /**
+ * Checks whether the map is empty.
+ *
+ * @param mapName map name
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Boolean> mapIsEmpty(String mapName);
+
+ /**
+ * Checks whether the map contains a key.
+ *
+ * @param mapName map name
+ * @param key key to check.
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Boolean> mapContainsKey(String mapName, K key);
+
+ /**
+ * Checks whether the map contains a value.
+ *
+ * @param mapName map name
+ * @param value The value to check.
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Boolean> mapContainsValue(String mapName, V value);
+
+ /**
+ * Gets a value from the map.
+ *
+ * @param mapName map name
+ * @param key The key to get.
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Versioned<V>> mapGet(String mapName, K key);
+
+ /**
+ * Updates the map.
+ *
+ * @param mapName map name
+ * @param key The key to set
+ * @param valueMatch match for checking existing value
+ * @param versionMatch match for checking existing version
+ * @param value new value
+ * @return A completable future to be completed with the result once complete
+ */
+ CompletableFuture<Result<UpdateResult<K, V>>> mapUpdate(
+ String mapName, K key, Match<V> valueMatch, Match<Long> versionMatch, V value);
+
+ /**
+ * Clears the map.
+ *
+ * @param mapName map name
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Result<Void>> mapClear(String mapName);
+
+ /**
+ * Gets a set of keys in the map.
+ *
+ * @param mapName map name
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Set<K>> mapKeySet(String mapName);
+
+ /**
+ * Gets a collection of values in the map.
+ *
+ * @param mapName map name
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Collection<Versioned<V>>> mapValues(String mapName);
+
+ /**
+ * Gets a set of entries in the map.
+ *
+ * @param mapName map name
+ * @return A completable future to be completed with the result once complete.
+ */
+ CompletableFuture<Set<Map.Entry<K, Versioned<V>>>> mapEntrySet(String mapName);
+
+ /**
+ * Atomically add the given value to current value of the specified counter.
+ *
+ * @param counterName counter name
+ * @param delta value to add
+ * @return updated value
+ */
+ CompletableFuture<Long> counterAddAndGet(String counterName, long delta);
+
+ /**
+ * Atomically add the given value to current value of the specified counter.
+ *
+ * @param counterName counter name
+ * @param delta value to add
+ * @return previous value
+ */
+ CompletableFuture<Long> counterGetAndAdd(String counterName, long delta);
+
+ /**
+ * Returns the current value of the specified atomic counter.
+ *
+ * @param counterName counter name
+ * @return current value
+ */
+ CompletableFuture<Long> counterGet(String counterName);
+
+ /**
+ * Returns the size of queue.
+ * @param queueName queue name
+ * @return queue size
+ */
+ CompletableFuture<Long> queueSize(String queueName);
+
+ /**
+ * Inserts an entry into the queue.
+ * @param queueName queue name
+ * @param entry queue entry
+ * @return void future
+ */
+ CompletableFuture<Void> queuePush(String queueName, byte[] entry);
+
+ /**
+ * Removes an entry from the queue if the queue is non-empty.
+ * @param queueName queue name
+ * @return entry future. Can be completed with null if queue is empty
+ */
+ CompletableFuture<byte[]> queuePop(String queueName);
+
+ /**
+ * Returns but does not remove an entry from the queue.
+ * @param queueName queue name
+ * @return entry. Can be null if queue is empty
+ */
+ CompletableFuture<byte[]> queuePeek(String queueName);
+
+ /**
+ * Prepare and commit the specified transaction.
+ *
+ * @param transaction transaction to commit (after preparation)
+ * @return A completable future to be completed with the result once complete
+ */
+ CompletableFuture<CommitResponse> prepareAndCommit(Transaction transaction);
+
+ /**
+ * Prepare the specified transaction for commit. A successful prepare implies
+ * all the affected resources are locked thus ensuring no concurrent updates can interfere.
+ *
+ * @param transaction transaction to prepare (for commit)
+ * @return A completable future to be completed with the result once complete. The future is completed
+ * with true if the transaction is successfully prepared i.e. all pre-conditions are met and
+ * applicable resources locked.
+ */
+ CompletableFuture<Boolean> prepare(Transaction transaction);
+
+ /**
+ * Commit the specified transaction. A successful commit implies
+ * all the updates are applied, are now durable and are now visible externally.
+ *
+ * @param transaction transaction to commit
+ * @return A completable future to be completed with the result once complete
+ */
+ CompletableFuture<CommitResponse> commit(Transaction transaction);
+
+ /**
+ * Rollback the specified transaction. A successful rollback implies
+ * all previously acquired locks for the affected resources are released.
+ *
+ * @param transaction transaction to rollback
+ * @return A completable future to be completed with the result once complete
+ */
+ CompletableFuture<Boolean> rollback(Transaction transaction);
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseSerializer.java
new file mode 100644
index 00000000..de734144
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseSerializer.java
@@ -0,0 +1,103 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.nio.ByteBuffer;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+
+import net.kuujo.copycat.cluster.internal.MemberInfo;
+import net.kuujo.copycat.raft.protocol.AppendRequest;
+import net.kuujo.copycat.raft.protocol.AppendResponse;
+import net.kuujo.copycat.raft.protocol.CommitRequest;
+import net.kuujo.copycat.raft.protocol.CommitResponse;
+import net.kuujo.copycat.raft.protocol.PollRequest;
+import net.kuujo.copycat.raft.protocol.PollResponse;
+import net.kuujo.copycat.raft.protocol.QueryRequest;
+import net.kuujo.copycat.raft.protocol.QueryResponse;
+import net.kuujo.copycat.raft.protocol.ReplicaInfo;
+import net.kuujo.copycat.raft.protocol.SyncRequest;
+import net.kuujo.copycat.raft.protocol.SyncResponse;
+import net.kuujo.copycat.raft.protocol.VoteRequest;
+import net.kuujo.copycat.raft.protocol.VoteResponse;
+import net.kuujo.copycat.util.serializer.SerializerConfig;
+
+/**
+ * Serializer for DatabaseManager's interaction with Copycat.
+ */
+public class DatabaseSerializer extends SerializerConfig {
+
+ private static final KryoNamespace COPYCAT = KryoNamespace.newBuilder()
+ .nextId(KryoNamespace.FLOATING_ID)
+ .register(AppendRequest.class)
+ .register(AppendResponse.class)
+ .register(SyncRequest.class)
+ .register(SyncResponse.class)
+ .register(VoteRequest.class)
+ .register(VoteResponse.class)
+ .register(PollRequest.class)
+ .register(PollResponse.class)
+ .register(QueryRequest.class)
+ .register(QueryResponse.class)
+ .register(CommitRequest.class)
+ .register(CommitResponse.class)
+ .register(ReplicaInfo.class)
+ .register(MemberInfo.class)
+ .build();
+
+ private static final KryoNamespace ONOS_STORE = KryoNamespace.newBuilder()
+ .nextId(KryoNamespace.FLOATING_ID)
+ .register(Versioned.class)
+ .register(DatabaseUpdate.class)
+ .register(DatabaseUpdate.Type.class)
+ .register(Result.class)
+ .register(UpdateResult.class)
+ .register(Result.Status.class)
+ .register(DefaultTransaction.class)
+ .register(Transaction.State.class)
+ .register(org.onosproject.store.consistent.impl.CommitResponse.class)
+ .register(Match.class)
+ .register(NodeId.class)
+ .build();
+
+ private static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.BASIC)
+ .register(COPYCAT)
+ .register(ONOS_STORE)
+ .build();
+ }
+ };
+
+ @Override
+ public ByteBuffer writeObject(Object object) {
+ return ByteBuffer.wrap(SERIALIZER.encode(object));
+ }
+
+ @Override
+ public <T> T readObject(ByteBuffer buffer) {
+ return SERIALIZER.decode(buffer);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseState.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseState.java
new file mode 100644
index 00000000..b3dd1c44
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseState.java
@@ -0,0 +1,114 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+
+import net.kuujo.copycat.state.Command;
+import net.kuujo.copycat.state.Initializer;
+import net.kuujo.copycat.state.Query;
+import net.kuujo.copycat.state.StateContext;
+
+/**
+ * Database state.
+ *
+ */
+public interface DatabaseState<K, V> {
+
+ /**
+ * Initializes the database state.
+ *
+ * @param context The map state context.
+ */
+ @Initializer
+ void init(StateContext<DatabaseState<K, V>> context);
+
+ @Query
+ Set<String> maps();
+
+ @Query
+ Map<String, Long> counters();
+
+ @Query
+ int mapSize(String mapName);
+
+ @Query
+ boolean mapIsEmpty(String mapName);
+
+ @Query
+ boolean mapContainsKey(String mapName, K key);
+
+ @Query
+ boolean mapContainsValue(String mapName, V value);
+
+ @Query
+ Versioned<V> mapGet(String mapName, K key);
+
+ @Command
+ Result<UpdateResult<K, V>> mapUpdate(String mapName, K key, Match<V> valueMatch, Match<Long> versionMatch, V value);
+
+ @Command
+ Result<Void> mapClear(String mapName);
+
+ @Query
+ Set<K> mapKeySet(String mapName);
+
+ @Query
+ Collection<Versioned<V>> mapValues(String mapName);
+
+ @Query
+ Set<Entry<K, Versioned<V>>> mapEntrySet(String mapName);
+
+ @Command
+ Long counterAddAndGet(String counterName, long delta);
+
+ @Command
+ Long counterGetAndAdd(String counterName, long delta);
+
+ @Query
+ Long queueSize(String queueName);
+
+ @Query
+ byte[] queuePeek(String queueName);
+
+ @Command
+ byte[] queuePop(String queueName);
+
+ @Command
+ void queuePush(String queueName, byte[] entry);
+
+ @Query
+ Long counterGet(String counterName);
+
+ @Command
+ CommitResponse prepareAndCommit(Transaction transaction);
+
+ @Command
+ boolean prepare(Transaction transaction);
+
+ @Command
+ CommitResponse commit(Transaction transaction);
+
+ @Command
+ boolean rollback(Transaction transaction);
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncAtomicCounter.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncAtomicCounter.java
new file mode 100644
index 00000000..7a439c34
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncAtomicCounter.java
@@ -0,0 +1,84 @@
+/*
+ * 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.store.consistent.impl;
+
+import org.onosproject.store.service.AsyncAtomicCounter;
+
+import java.util.concurrent.CompletableFuture;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation for a distributed AsyncAtomicCounter backed by
+ * partitioned Raft DB.
+ * <p>
+ * The initial value will be zero.
+ */
+public class DefaultAsyncAtomicCounter implements AsyncAtomicCounter {
+
+ private final String name;
+ private final Database database;
+ private final MeteringAgent monitor;
+
+ private static final String PRIMITIVE_NAME = "atomicCounter";
+ private static final String INCREMENT_AND_GET = "incrementAndGet";
+ private static final String GET_AND_INCREMENT = "getAndIncrement";
+ private static final String GET_AND_ADD = "getAndAdd";
+ private static final String ADD_AND_GET = "addAndGet";
+ private static final String GET = "get";
+
+ public DefaultAsyncAtomicCounter(String name,
+ Database database,
+ boolean meteringEnabled) {
+ this.name = checkNotNull(name);
+ this.database = checkNotNull(database);
+ this.monitor = new MeteringAgent(PRIMITIVE_NAME, name, meteringEnabled);
+ }
+
+ @Override
+ public CompletableFuture<Long> incrementAndGet() {
+ final MeteringAgent.Context timer = monitor.startTimer(INCREMENT_AND_GET);
+ return addAndGet(1L)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Long> get() {
+ final MeteringAgent.Context timer = monitor.startTimer(GET);
+ return database.counterGet(name)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Long> getAndIncrement() {
+ final MeteringAgent.Context timer = monitor.startTimer(GET_AND_INCREMENT);
+ return getAndAdd(1L)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Long> getAndAdd(long delta) {
+ final MeteringAgent.Context timer = monitor.startTimer(GET_AND_ADD);
+ return database.counterGetAndAdd(name, delta)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Long> addAndGet(long delta) {
+ final MeteringAgent.Context timer = monitor.startTimer(ADD_AND_GET);
+ return database.counterAddAndGet(name, delta)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncConsistentMap.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncConsistentMap.java
new file mode 100644
index 00000000..0ea66861
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAsyncConsistentMap.java
@@ -0,0 +1,465 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Maps;
+import org.onlab.util.HexString;
+import org.onlab.util.SharedExecutors;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.store.service.AsyncConsistentMap;
+import org.onosproject.store.service.ConsistentMapException;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.store.consistent.impl.StateMachineUpdate.Target.MAP_UPDATE;
+import static org.onosproject.store.consistent.impl.StateMachineUpdate.Target.TX_COMMIT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * AsyncConsistentMap implementation that is backed by a Raft consensus
+ * based database.
+ *
+ * @param <K> type of key.
+ * @param <V> type of value.
+ */
+public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> {
+
+ private final String name;
+ private final ApplicationId applicationId;
+ private final Database database;
+ private final Serializer serializer;
+ private final boolean readOnly;
+ private final boolean purgeOnUninstall;
+
+ private static final String PRIMITIVE_NAME = "consistentMap";
+ private static final String SIZE = "size";
+ private static final String IS_EMPTY = "isEmpty";
+ private static final String CONTAINS_KEY = "containsKey";
+ private static final String CONTAINS_VALUE = "containsValue";
+ private static final String GET = "get";
+ private static final String COMPUTE_IF = "computeIf";
+ private static final String PUT = "put";
+ private static final String PUT_AND_GET = "putAndGet";
+ private static final String PUT_IF_ABSENT = "putIfAbsent";
+ private static final String REMOVE = "remove";
+ private static final String CLEAR = "clear";
+ private static final String KEY_SET = "keySet";
+ private static final String VALUES = "values";
+ private static final String ENTRY_SET = "entrySet";
+ private static final String REPLACE = "replace";
+ private static final String COMPUTE_IF_ABSENT = "computeIfAbsent";
+
+ private final Set<MapEventListener<K, V>> listeners = new CopyOnWriteArraySet<>();
+
+ private final Logger log = getLogger(getClass());
+ private final MeteringAgent monitor;
+
+ private static final String ERROR_NULL_KEY = "Key cannot be null";
+ private static final String ERROR_NULL_VALUE = "Null values are not allowed";
+
+ private final LoadingCache<K, String> keyCache = CacheBuilder.newBuilder()
+ .softValues()
+ .build(new CacheLoader<K, String>() {
+
+ @Override
+ public String load(K key) {
+ return HexString.toHexString(serializer.encode(key));
+ }
+ });
+
+ protected K dK(String key) {
+ return serializer.decode(HexString.fromHexString(key));
+ }
+
+ public DefaultAsyncConsistentMap(String name,
+ ApplicationId applicationId,
+ Database database,
+ Serializer serializer,
+ boolean readOnly,
+ boolean purgeOnUninstall,
+ boolean meteringEnabled) {
+ this.name = checkNotNull(name, "map name cannot be null");
+ this.applicationId = applicationId;
+ this.database = checkNotNull(database, "database cannot be null");
+ this.serializer = checkNotNull(serializer, "serializer cannot be null");
+ this.readOnly = readOnly;
+ this.purgeOnUninstall = purgeOnUninstall;
+ this.database.registerConsumer(update -> {
+ SharedExecutors.getSingleThreadExecutor().execute(() -> {
+ if (listeners.isEmpty()) {
+ return;
+ }
+ try {
+ if (update.target() == MAP_UPDATE) {
+ Result<UpdateResult<String, byte[]>> result = update.output();
+ if (result.success() && result.value().mapName().equals(name)) {
+ MapEvent<K, V> mapEvent = result.value()
+ .<K, V>map(this::dK,
+ v -> serializer.decode(Tools.copyOf(v)))
+ .toMapEvent();
+ notifyListeners(mapEvent);
+ }
+ } else if (update.target() == TX_COMMIT) {
+ CommitResponse response = update.output();
+ if (response.success()) {
+ response.updates().forEach(u -> {
+ if (u.mapName().equals(name)) {
+ MapEvent<K, V> mapEvent =
+ u.<K, V>map(this::dK,
+ v -> serializer.decode(Tools.copyOf(v)))
+ .toMapEvent();
+ notifyListeners(mapEvent);
+ }
+ });
+ }
+ }
+ } catch (Exception e) {
+ log.warn("Error notifying listeners", e);
+ }
+ });
+ });
+ this.monitor = new MeteringAgent(PRIMITIVE_NAME, name, meteringEnabled);
+ }
+
+ /**
+ * Returns this map name.
+ * @return map name
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the serializer for map entries.
+ * @return map entry serializer
+ */
+ public Serializer serializer() {
+ return serializer;
+ }
+
+ /**
+ * Returns the applicationId owning this map.
+ * @return application Id
+ */
+ public ApplicationId applicationId() {
+ return applicationId;
+ }
+
+ /**
+ * Returns whether the map entries should be purged when the application
+ * owning it is uninstalled.
+ * @return true is map needs to cleared on app uninstall; false otherwise
+ */
+ public boolean purgeOnUninstall() {
+ return purgeOnUninstall;
+ }
+
+ @Override
+ public CompletableFuture<Integer> size() {
+ final MeteringAgent.Context timer = monitor.startTimer(SIZE);
+ return database.mapSize(name)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> isEmpty() {
+ final MeteringAgent.Context timer = monitor.startTimer(IS_EMPTY);
+ return database.mapIsEmpty(name)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> containsKey(K key) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_KEY);
+ return database.mapContainsKey(name, keyCache.getUnchecked(key))
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> containsValue(V value) {
+ checkNotNull(value, ERROR_NULL_VALUE);
+ final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_VALUE);
+ return database.mapContainsValue(name, serializer.encode(value))
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> get(K key) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ final MeteringAgent.Context timer = monitor.startTimer(GET);
+ return database.mapGet(name, keyCache.getUnchecked(key))
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v != null ? v.map(serializer::decode) : null);
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(mappingFunction, "Mapping function cannot be null");
+ final MeteringAgent.Context timer = monitor.startTimer(COMPUTE_IF_ABSENT);
+ return updateAndGet(key, Match.ifNull(), Match.any(), mappingFunction.apply(key))
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v.newValue());
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return computeIf(key, Objects::nonNull, remappingFunction);
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return computeIf(key, v -> true, remappingFunction);
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> computeIf(K key,
+ Predicate<? super V> condition,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(condition, "predicate function cannot be null");
+ checkNotNull(remappingFunction, "Remapping function cannot be null");
+ final MeteringAgent.Context timer = monitor.startTimer(COMPUTE_IF);
+ return get(key).thenCompose(r1 -> {
+ V existingValue = r1 == null ? null : r1.value();
+ // if the condition evaluates to false, return existing value.
+ if (!condition.test(existingValue)) {
+ return CompletableFuture.completedFuture(r1);
+ }
+
+ AtomicReference<V> computedValue = new AtomicReference<>();
+ // if remappingFunction throws an exception, return the exception.
+ try {
+ computedValue.set(remappingFunction.apply(key, existingValue));
+ } catch (Exception e) {
+ return Tools.exceptionalFuture(e);
+ }
+ if (computedValue.get() == null && r1 == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ Match<V> valueMatcher = r1 == null ? Match.ifNull() : Match.any();
+ Match<Long> versionMatcher = r1 == null ? Match.any() : Match.ifValue(r1.version());
+ return updateAndGet(key, valueMatcher, versionMatcher, computedValue.get())
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> {
+ if (v.updated()) {
+ return v.newValue();
+ } else {
+ throw new ConsistentMapException.ConcurrentModification();
+ }
+ });
+ });
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> put(K key, V value) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ final MeteringAgent.Context timer = monitor.startTimer(PUT);
+ return updateAndGet(key, Match.any(), Match.any(), value).thenApply(v -> v.oldValue())
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> putAndGet(K key, V value) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ final MeteringAgent.Context timer = monitor.startTimer(PUT_AND_GET);
+ return updateAndGet(key, Match.any(), Match.any(), value).thenApply(v -> v.newValue())
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> remove(K key) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ final MeteringAgent.Context timer = monitor.startTimer(REMOVE);
+ return updateAndGet(key, Match.any(), Match.any(), null).thenApply(v -> v.oldValue())
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Void> clear() {
+ checkIfUnmodifiable();
+ final MeteringAgent.Context timer = monitor.startTimer(CLEAR);
+ return database.mapClear(name).thenApply(this::unwrapResult)
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Set<K>> keySet() {
+ final MeteringAgent.Context timer = monitor.startTimer(KEY_SET);
+ return database.mapKeySet(name)
+ .thenApply(s -> s
+ .stream()
+ .map(this::dK)
+ .collect(Collectors.toSet()))
+ .whenComplete((r, e) -> timer.stop(e));
+ }
+
+ @Override
+ public CompletableFuture<Collection<Versioned<V>>> values() {
+ final MeteringAgent.Context timer = monitor.startTimer(VALUES);
+ return database.mapValues(name)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(c -> c
+ .stream()
+ .map(v -> v.<V>map(serializer::decode))
+ .collect(Collectors.toList()));
+ }
+
+ @Override
+ public CompletableFuture<Set<Entry<K, Versioned<V>>>> entrySet() {
+ final MeteringAgent.Context timer = monitor.startTimer(ENTRY_SET);
+ return database.mapEntrySet(name)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(s -> s
+ .stream()
+ .map(this::mapRawEntry)
+ .collect(Collectors.toSet()));
+ }
+
+ @Override
+ public CompletableFuture<Versioned<V>> putIfAbsent(K key, V value) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ final MeteringAgent.Context timer = monitor.startTimer(PUT_IF_ABSENT);
+ return updateAndGet(key, Match.ifNull(), Match.any(), value)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v.oldValue());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> remove(K key, V value) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ final MeteringAgent.Context timer = monitor.startTimer(REMOVE);
+ return updateAndGet(key, Match.ifValue(value), Match.any(), null)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v.updated());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> remove(K key, long version) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ final MeteringAgent.Context timer = monitor.startTimer(REMOVE);
+ return updateAndGet(key, Match.any(), Match.ifValue(version), null)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v.updated());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> replace(K key, V oldValue, V newValue) {
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(oldValue, ERROR_NULL_VALUE);
+ checkNotNull(newValue, ERROR_NULL_VALUE);
+ final MeteringAgent.Context timer = monitor.startTimer(REPLACE);
+ return updateAndGet(key, Match.ifValue(oldValue), Match.any(), newValue)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v.updated());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> replace(K key, long oldVersion, V newValue) {
+ final MeteringAgent.Context timer = monitor.startTimer(REPLACE);
+ return updateAndGet(key, Match.any(), Match.ifValue(oldVersion), newValue)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenApply(v -> v.updated());
+ }
+
+ private Map.Entry<K, Versioned<V>> mapRawEntry(Map.Entry<String, Versioned<byte[]>> e) {
+ return Maps.immutableEntry(dK(e.getKey()), e.getValue().<V>map(serializer::decode));
+ }
+
+ private CompletableFuture<UpdateResult<K, V>> updateAndGet(K key,
+ Match<V> oldValueMatch,
+ Match<Long> oldVersionMatch,
+ V value) {
+ checkIfUnmodifiable();
+ return database.mapUpdate(name,
+ keyCache.getUnchecked(key),
+ oldValueMatch.map(serializer::encode),
+ oldVersionMatch,
+ value == null ? null : serializer.encode(value))
+ .thenApply(this::unwrapResult)
+ .thenApply(r -> r.<K, V>map(this::dK, serializer::decode));
+ }
+
+ private <T> T unwrapResult(Result<T> result) {
+ if (result.status() == Result.Status.LOCKED) {
+ throw new ConsistentMapException.ConcurrentModification();
+ } else if (result.success()) {
+ return result.value();
+ } else {
+ throw new IllegalStateException("Must not be here");
+ }
+ }
+
+ private void checkIfUnmodifiable() {
+ if (readOnly) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public void addListener(MapEventListener<K, V> listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(MapEventListener<K, V> listener) {
+ listeners.remove(listener);
+ }
+
+ protected void notifyListeners(MapEvent<K, V> event) {
+ if (event == null) {
+ return;
+ }
+ listeners.forEach(listener -> {
+ try {
+ listener.event(event);
+ } catch (Exception e) {
+ log.warn("Failure notifying listener about {}", event, e);
+ }
+ });
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounter.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounter.java
new file mode 100644
index 00000000..64886e41
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounter.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.store.consistent.impl;
+
+import org.onosproject.store.service.AsyncAtomicCounter;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageException;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Default implementation for a distributed AtomicCounter backed by
+ * partitioned Raft DB.
+ * <p>
+ * The initial value will be zero.
+ */
+public class DefaultAtomicCounter implements AtomicCounter {
+
+ private static final int OPERATION_TIMEOUT_MILLIS = 5000;
+
+ private final AsyncAtomicCounter asyncCounter;
+
+ public DefaultAtomicCounter(String name,
+ Database database,
+ boolean meteringEnabled) {
+ asyncCounter = new DefaultAsyncAtomicCounter(name, database, meteringEnabled);
+ }
+
+ @Override
+ public long incrementAndGet() {
+ return complete(asyncCounter.incrementAndGet());
+ }
+
+ @Override
+ public long getAndIncrement() {
+ return complete(asyncCounter.getAndIncrement());
+ }
+
+ @Override
+ public long getAndAdd(long delta) {
+ return complete(asyncCounter.getAndAdd(delta));
+ }
+
+ @Override
+ public long addAndGet(long delta) {
+ return complete(asyncCounter.getAndAdd(delta));
+ }
+
+ @Override
+ public long get() {
+ return complete(asyncCounter.get());
+ }
+
+ private static <T> T complete(CompletableFuture<T> future) {
+ try {
+ return future.get(OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new StorageException.Interrupted();
+ } catch (TimeoutException e) {
+ throw new StorageException.Timeout();
+ } catch (ExecutionException e) {
+ throw new StorageException(e.getCause());
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounterBuilder.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounterBuilder.java
new file mode 100644
index 00000000..dba4443b
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicCounterBuilder.java
@@ -0,0 +1,77 @@
+/*
+ * 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.store.consistent.impl;
+
+import org.onosproject.store.service.AsyncAtomicCounter;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.AtomicCounterBuilder;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Default implementation of AtomicCounterBuilder.
+ */
+public class DefaultAtomicCounterBuilder implements AtomicCounterBuilder {
+
+ private String name;
+ private boolean partitionsEnabled = true;
+ private final Database partitionedDatabase;
+ private final Database inMemoryDatabase;
+ private boolean metering = true;
+
+ public DefaultAtomicCounterBuilder(Database inMemoryDatabase, Database partitionedDatabase) {
+ this.inMemoryDatabase = inMemoryDatabase;
+ this.partitionedDatabase = partitionedDatabase;
+ }
+
+ @Override
+ public AtomicCounterBuilder withName(String name) {
+ checkArgument(name != null && !name.isEmpty());
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public AtomicCounterBuilder withPartitionsDisabled() {
+ partitionsEnabled = false;
+ return this;
+ }
+
+ @Override
+ public AtomicCounter build() {
+ validateInputs();
+ Database database = partitionsEnabled ? partitionedDatabase : inMemoryDatabase;
+ return new DefaultAtomicCounter(name, database, metering);
+ }
+
+ @Override
+ public AsyncAtomicCounter buildAsyncCounter() {
+ validateInputs();
+ Database database = partitionsEnabled ? partitionedDatabase : inMemoryDatabase;
+ return new DefaultAsyncAtomicCounter(name, database, metering);
+ }
+
+ @Override
+ public AtomicCounterBuilder withMeteringDisabled() {
+ metering = false;
+ return this;
+ }
+
+ private void validateInputs() {
+ checkState(name != null, "name must be specified");
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValue.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValue.java
new file mode 100644
index 00000000..e8c93f31
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValue.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.store.consistent.impl;
+
+import org.onosproject.store.service.AtomicValue;
+import org.onosproject.store.service.AtomicValueEvent;
+import org.onosproject.store.service.AtomicValueEventListener;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.Versioned;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Default implementation of AtomicValue.
+ *
+ * @param <V> value type
+ */
+public class DefaultAtomicValue<V> implements AtomicValue<V> {
+
+ private final Set<AtomicValueEventListener<V>> listeners = new CopyOnWriteArraySet<>();
+ private final ConsistentMap<String, byte[]> valueMap;
+ private final String name;
+ private final Serializer serializer;
+ private final MapEventListener<String, byte[]> mapEventListener = new InternalMapEventListener();
+ private final MeteringAgent monitor;
+
+ private static final String COMPONENT_NAME = "atomicValue";
+ private static final String GET = "get";
+ private static final String GET_AND_SET = "getAndSet";
+ private static final String COMPARE_AND_SET = "compareAndSet";
+
+ public DefaultAtomicValue(ConsistentMap<String, byte[]> valueMap,
+ String name,
+ boolean meteringEnabled,
+ Serializer serializer) {
+ this.valueMap = valueMap;
+ this.name = name;
+ this.serializer = serializer;
+ this.monitor = new MeteringAgent(COMPONENT_NAME, name, meteringEnabled);
+ }
+
+ @Override
+ public boolean compareAndSet(V expect, V update) {
+ final MeteringAgent.Context newTimer = monitor.startTimer(COMPARE_AND_SET);
+ try {
+ if (expect == null) {
+ if (update == null) {
+ return true;
+ }
+ return valueMap.putIfAbsent(name, serializer.encode(update)) == null;
+ } else {
+ if (update == null) {
+ return valueMap.remove(name, serializer.encode(expect));
+ }
+ return valueMap.replace(name, serializer.encode(expect), serializer.encode(update));
+ }
+ } finally {
+ newTimer.stop(null);
+ }
+ }
+
+ @Override
+ public V get() {
+ final MeteringAgent.Context newTimer = monitor.startTimer(GET);
+ try {
+ Versioned<byte[]> rawValue = valueMap.get(name);
+ return rawValue == null ? null : serializer.decode(rawValue.value());
+ } finally {
+ newTimer.stop(null);
+ }
+ }
+
+ @Override
+ public V getAndSet(V value) {
+ final MeteringAgent.Context newTimer = monitor.startTimer(GET_AND_SET);
+ try {
+ Versioned<byte[]> previousValue = value == null ?
+ valueMap.remove(name) : valueMap.put(name, serializer.encode(value));
+ return previousValue == null ? null : serializer.decode(previousValue.value());
+ } finally {
+ newTimer.stop(null);
+ }
+ }
+
+ @Override
+ public void set(V value) {
+ getAndSet(value);
+ }
+
+ @Override
+ public void addListener(AtomicValueEventListener<V> listener) {
+ synchronized (listeners) {
+ if (listeners.add(listener)) {
+ if (listeners.size() == 1) {
+ valueMap.addListener(mapEventListener);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeListener(AtomicValueEventListener<V> listener) {
+ synchronized (listeners) {
+ if (listeners.remove(listener)) {
+ if (listeners.size() == 0) {
+ valueMap.removeListener(mapEventListener);
+ }
+ }
+ }
+ }
+
+ private class InternalMapEventListener implements MapEventListener<String, byte[]> {
+
+ @Override
+ public void event(MapEvent<String, byte[]> mapEvent) {
+ V newValue = mapEvent.type() == MapEvent.Type.REMOVE ? null : serializer.decode(mapEvent.value().value());
+ AtomicValueEvent<V> atomicValueEvent = new AtomicValueEvent<>(name, AtomicValueEvent.Type.UPDATE, newValue);
+ listeners.forEach(l -> l.event(atomicValueEvent));
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValueBuilder.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValueBuilder.java
new file mode 100644
index 00000000..b39004b3
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultAtomicValueBuilder.java
@@ -0,0 +1,71 @@
+/*
+ * 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.store.consistent.impl;
+
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.AtomicValue;
+import org.onosproject.store.service.AtomicValueBuilder;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.Serializer;
+
+/**
+ * Default implementation of AtomicValueBuilder.
+ *
+ * @param <V> value type
+ */
+public class DefaultAtomicValueBuilder<V> implements AtomicValueBuilder<V> {
+
+ private Serializer serializer;
+ private String name;
+ private ConsistentMapBuilder<String, byte[]> mapBuilder;
+ private boolean metering = true;
+
+ public DefaultAtomicValueBuilder(DatabaseManager manager) {
+ mapBuilder = manager.<String, byte[]>consistentMapBuilder()
+ .withName("onos-atomic-values")
+ .withMeteringDisabled()
+ .withSerializer(Serializer.using(KryoNamespaces.BASIC));
+ }
+
+ @Override
+ public AtomicValueBuilder<V> withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public AtomicValueBuilder<V> withSerializer(Serializer serializer) {
+ this.serializer = serializer;
+ return this;
+ }
+
+ @Override
+ public AtomicValueBuilder<V> withPartitionsDisabled() {
+ mapBuilder.withPartitionsDisabled();
+ return this;
+ }
+
+ @Override
+ public AtomicValueBuilder<V> withMeteringDisabled() {
+ metering = false;
+ return this;
+ }
+
+ @Override
+ public AtomicValue<V> build() {
+ return new DefaultAtomicValue<>(mapBuilder.build(), name, metering, serializer);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMap.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMap.java
new file mode 100644
index 00000000..6f7b5487
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMap.java
@@ -0,0 +1,204 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.Set;
+
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapException;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Versioned;
+
+/**
+ * ConsistentMap implementation that is backed by a Raft consensus
+ * based database.
+ *
+ * @param <K> type of key.
+ * @param <V> type of value.
+ */
+public class DefaultConsistentMap<K, V> implements ConsistentMap<K, V> {
+
+ private static final int OPERATION_TIMEOUT_MILLIS = 5000;
+
+ private final DefaultAsyncConsistentMap<K, V> asyncMap;
+ private Map<K, V> javaMap;
+
+ public String name() {
+ return asyncMap.name();
+ }
+
+ public DefaultConsistentMap(DefaultAsyncConsistentMap<K, V> asyncMap) {
+ this.asyncMap = asyncMap;
+ }
+
+ @Override
+ public int size() {
+ return complete(asyncMap.size());
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return complete(asyncMap.isEmpty());
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ return complete(asyncMap.containsKey(key));
+ }
+
+ @Override
+ public boolean containsValue(V value) {
+ return complete(asyncMap.containsValue(value));
+ }
+
+ @Override
+ public Versioned<V> get(K key) {
+ return complete(asyncMap.get(key));
+ }
+
+ @Override
+ public Versioned<V> computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ return complete(asyncMap.computeIfAbsent(key, mappingFunction));
+ }
+
+ @Override
+ public Versioned<V> computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return complete(asyncMap.computeIfPresent(key, remappingFunction));
+ }
+
+ @Override
+ public Versioned<V> compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return complete(asyncMap.compute(key, remappingFunction));
+ }
+
+ @Override
+ public Versioned<V> computeIf(K key,
+ Predicate<? super V> condition,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return complete(asyncMap.computeIf(key, condition, remappingFunction));
+ }
+
+ @Override
+ public Versioned<V> put(K key, V value) {
+ return complete(asyncMap.put(key, value));
+ }
+
+ @Override
+ public Versioned<V> putAndGet(K key, V value) {
+ return complete(asyncMap.putAndGet(key, value));
+ }
+
+ @Override
+ public Versioned<V> remove(K key) {
+ return complete(asyncMap.remove(key));
+ }
+
+ @Override
+ public void clear() {
+ complete(asyncMap.clear());
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return complete(asyncMap.keySet());
+ }
+
+ @Override
+ public Collection<Versioned<V>> values() {
+ return complete(asyncMap.values());
+ }
+
+ @Override
+ public Set<Entry<K, Versioned<V>>> entrySet() {
+ return complete(asyncMap.entrySet());
+ }
+
+ @Override
+ public Versioned<V> putIfAbsent(K key, V value) {
+ return complete(asyncMap.putIfAbsent(key, value));
+ }
+
+ @Override
+ public boolean remove(K key, V value) {
+ return complete(asyncMap.remove(key, value));
+ }
+
+ @Override
+ public boolean remove(K key, long version) {
+ return complete(asyncMap.remove(key, version));
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return complete(asyncMap.replace(key, oldValue, newValue));
+ }
+
+ @Override
+ public boolean replace(K key, long oldVersion, V newValue) {
+ return complete(asyncMap.replace(key, oldVersion, newValue));
+ }
+
+ private static <T> T complete(CompletableFuture<T> future) {
+ try {
+ return future.get(OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new ConsistentMapException.Interrupted();
+ } catch (TimeoutException e) {
+ throw new ConsistentMapException.Timeout();
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof ConsistentMapException) {
+ throw (ConsistentMapException) e.getCause();
+ } else {
+ throw new ConsistentMapException(e.getCause());
+ }
+ }
+ }
+
+ @Override
+ public void addListener(MapEventListener<K, V> listener) {
+ asyncMap.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(MapEventListener<K, V> listener) {
+ asyncMap.addListener(listener);
+ }
+
+ @Override
+ public Map<K, V> asJavaMap() {
+ synchronized (this) {
+ if (javaMap == null) {
+ javaMap = new ConsistentMapBackedJavaMap<>(this);
+ }
+ }
+ return javaMap;
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMapBuilder.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMapBuilder.java
new file mode 100644
index 00000000..0e11794e
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultConsistentMapBuilder.java
@@ -0,0 +1,141 @@
+/*
+ * 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.store.consistent.impl;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.store.service.AsyncConsistentMap;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.Serializer;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Default Consistent Map builder.
+ *
+ * @param <K> type for map key
+ * @param <V> type for map value
+ */
+public class DefaultConsistentMapBuilder<K, V> implements ConsistentMapBuilder<K, V> {
+
+ private Serializer serializer;
+ private String name;
+ private ApplicationId applicationId;
+ private boolean purgeOnUninstall = false;
+ private boolean partitionsEnabled = true;
+ private boolean readOnly = false;
+ private boolean metering = true;
+ private boolean relaxedReadConsistency = false;
+ private final DatabaseManager manager;
+
+ public DefaultConsistentMapBuilder(DatabaseManager manager) {
+ this.manager = manager;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withName(String name) {
+ checkArgument(name != null && !name.isEmpty());
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withApplicationId(ApplicationId id) {
+ checkArgument(id != null);
+ this.applicationId = id;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withPurgeOnUninstall() {
+ purgeOnUninstall = true;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withMeteringDisabled() {
+ metering = false;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withSerializer(Serializer serializer) {
+ checkArgument(serializer != null);
+ this.serializer = serializer;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withPartitionsDisabled() {
+ partitionsEnabled = false;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withUpdatesDisabled() {
+ readOnly = true;
+ return this;
+ }
+
+ @Override
+ public ConsistentMapBuilder<K, V> withRelaxedReadConsistency() {
+ relaxedReadConsistency = true;
+ return this;
+ }
+
+ private void validateInputs() {
+ checkState(name != null, "name must be specified");
+ checkState(serializer != null, "serializer must be specified");
+ if (purgeOnUninstall) {
+ checkState(applicationId != null, "ApplicationId must be specified when purgeOnUninstall is enabled");
+ }
+ }
+
+ @Override
+ public ConsistentMap<K, V> build() {
+ return new DefaultConsistentMap<>(buildAndRegisterMap());
+ }
+
+ @Override
+ public AsyncConsistentMap<K, V> buildAsyncMap() {
+ return buildAndRegisterMap();
+ }
+
+ private DefaultAsyncConsistentMap<K, V> buildAndRegisterMap() {
+ validateInputs();
+ Database database = partitionsEnabled ? manager.partitionedDatabase : manager.inMemoryDatabase;
+ if (relaxedReadConsistency) {
+ return manager.registerMap(
+ new AsyncCachingConsistentMap<>(name,
+ applicationId,
+ database,
+ serializer,
+ readOnly,
+ purgeOnUninstall,
+ metering));
+ } else {
+ return manager.registerMap(
+ new DefaultAsyncConsistentMap<>(name,
+ applicationId,
+ database,
+ serializer,
+ readOnly,
+ purgeOnUninstall,
+ metering));
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabase.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabase.java
new file mode 100644
index 00000000..4d9776ee
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabase.java
@@ -0,0 +1,243 @@
+/*
+ * 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.store.consistent.impl;
+
+import net.kuujo.copycat.state.StateMachine;
+import net.kuujo.copycat.resource.internal.AbstractResource;
+import net.kuujo.copycat.resource.internal.ResourceManager;
+import net.kuujo.copycat.state.internal.DefaultStateMachine;
+import net.kuujo.copycat.util.concurrent.Futures;
+import net.kuujo.copycat.util.function.TriConsumer;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Default database.
+ */
+public class DefaultDatabase extends AbstractResource<Database> implements Database {
+ private final StateMachine<DatabaseState<String, byte[]>> stateMachine;
+ private DatabaseProxy<String, byte[]> proxy;
+ private final Set<Consumer<StateMachineUpdate>> consumers = Sets.newCopyOnWriteArraySet();
+ private final TriConsumer<String, Object, Object> watcher = new InternalStateMachineWatcher();
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public DefaultDatabase(ResourceManager context) {
+ super(context);
+ this.stateMachine = new DefaultStateMachine(context,
+ DatabaseState.class,
+ DefaultDatabaseState.class,
+ DefaultDatabase.class.getClassLoader());
+ this.stateMachine.addStartupTask(() -> {
+ stateMachine.registerWatcher(watcher);
+ return CompletableFuture.completedFuture(null);
+ });
+ this.stateMachine.addShutdownTask(() -> {
+ stateMachine.unregisterWatcher(watcher);
+ return CompletableFuture.completedFuture(null);
+ });
+ }
+
+ /**
+ * If the database is closed, returning a failed CompletableFuture. Otherwise, calls the given supplier to
+ * return the completed future result.
+ *
+ * @param supplier The supplier to call if the database is open.
+ * @param <T> The future result type.
+ * @return A completable future that if this database is closed is immediately failed.
+ */
+ protected <T> CompletableFuture<T> checkOpen(Supplier<CompletableFuture<T>> supplier) {
+ if (proxy == null) {
+ return Futures.exceptionalFuture(new IllegalStateException("Database closed"));
+ }
+ return supplier.get();
+ }
+
+ @Override
+ public CompletableFuture<Set<String>> maps() {
+ return checkOpen(() -> proxy.maps());
+ }
+
+ @Override
+ public CompletableFuture<Map<String, Long>> counters() {
+ return checkOpen(() -> proxy.counters());
+ }
+
+ @Override
+ public CompletableFuture<Integer> mapSize(String mapName) {
+ return checkOpen(() -> proxy.mapSize(mapName));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> mapIsEmpty(String mapName) {
+ return checkOpen(() -> proxy.mapIsEmpty(mapName));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> mapContainsKey(String mapName, String key) {
+ return checkOpen(() -> proxy.mapContainsKey(mapName, key));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> mapContainsValue(String mapName, byte[] value) {
+ return checkOpen(() -> proxy.mapContainsValue(mapName, value));
+ }
+
+ @Override
+ public CompletableFuture<Versioned<byte[]>> mapGet(String mapName, String key) {
+ return checkOpen(() -> proxy.mapGet(mapName, key));
+ }
+
+ @Override
+ public CompletableFuture<Result<UpdateResult<String, byte[]>>> mapUpdate(
+ String mapName, String key, Match<byte[]> valueMatch, Match<Long> versionMatch, byte[] value) {
+ return checkOpen(() -> proxy.mapUpdate(mapName, key, valueMatch, versionMatch, value));
+ }
+
+ @Override
+ public CompletableFuture<Result<Void>> mapClear(String mapName) {
+ return checkOpen(() -> proxy.mapClear(mapName));
+ }
+
+ @Override
+ public CompletableFuture<Set<String>> mapKeySet(String mapName) {
+ return checkOpen(() -> proxy.mapKeySet(mapName));
+ }
+
+ @Override
+ public CompletableFuture<Collection<Versioned<byte[]>>> mapValues(String mapName) {
+ return checkOpen(() -> proxy.mapValues(mapName));
+ }
+
+ @Override
+ public CompletableFuture<Set<Map.Entry<String, Versioned<byte[]>>>> mapEntrySet(String mapName) {
+ return checkOpen(() -> proxy.mapEntrySet(mapName));
+ }
+
+ @Override
+ public CompletableFuture<Long> counterGet(String counterName) {
+ return checkOpen(() -> proxy.counterGet(counterName));
+ }
+
+ @Override
+ public CompletableFuture<Long> counterAddAndGet(String counterName, long delta) {
+ return checkOpen(() -> proxy.counterAddAndGet(counterName, delta));
+ }
+
+ @Override
+ public CompletableFuture<Long> counterGetAndAdd(String counterName, long delta) {
+ return checkOpen(() -> proxy.counterGetAndAdd(counterName, delta));
+ }
+
+ @Override
+ public CompletableFuture<Long> queueSize(String queueName) {
+ return checkOpen(() -> proxy.queueSize(queueName));
+ }
+
+ @Override
+ public CompletableFuture<Void> queuePush(String queueName, byte[] entry) {
+ return checkOpen(() -> proxy.queuePush(queueName, entry));
+ }
+
+ @Override
+ public CompletableFuture<byte[]> queuePop(String queueName) {
+ return checkOpen(() -> proxy.queuePop(queueName));
+ }
+
+ @Override
+ public CompletableFuture<byte[]> queuePeek(String queueName) {
+ return checkOpen(() -> proxy.queuePeek(queueName));
+ }
+
+ @Override
+ public CompletableFuture<CommitResponse> prepareAndCommit(Transaction transaction) {
+ return checkOpen(() -> proxy.prepareAndCommit(transaction));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> prepare(Transaction transaction) {
+ return checkOpen(() -> proxy.prepare(transaction));
+ }
+
+ @Override
+ public CompletableFuture<CommitResponse> commit(Transaction transaction) {
+ return checkOpen(() -> proxy.commit(transaction));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> rollback(Transaction transaction) {
+ return checkOpen(() -> proxy.rollback(transaction));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public synchronized CompletableFuture<Database> open() {
+ return runStartupTasks()
+ .thenCompose(v -> stateMachine.open())
+ .thenRun(() -> {
+ this.proxy = stateMachine.createProxy(DatabaseProxy.class, this.getClass().getClassLoader());
+ })
+ .thenApply(v -> null);
+ }
+
+ @Override
+ public synchronized CompletableFuture<Void> close() {
+ proxy = null;
+ return stateMachine.close()
+ .thenCompose(v -> runShutdownTasks());
+ }
+
+ @Override
+ public int hashCode() {
+ return name().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Database) {
+ return name().equals(((Database) other).name());
+ }
+ return false;
+ }
+
+ @Override
+ public void registerConsumer(Consumer<StateMachineUpdate> consumer) {
+ consumers.add(consumer);
+ }
+
+ @Override
+ public void unregisterConsumer(Consumer<StateMachineUpdate> consumer) {
+ consumers.remove(consumer);
+ }
+
+ private class InternalStateMachineWatcher implements TriConsumer<String, Object, Object> {
+ @Override
+ public void accept(String name, Object input, Object output) {
+ StateMachineUpdate update = new StateMachineUpdate(name, input, output);
+ consumers.forEach(consumer -> consumer.accept(update));
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabaseState.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabaseState.java
new file mode 100644
index 00000000..9d3505bd
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDatabaseState.java
@@ -0,0 +1,368 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.Set;
+
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import net.kuujo.copycat.state.Initializer;
+import net.kuujo.copycat.state.StateContext;
+
+/**
+ * Default database state.
+ */
+public class DefaultDatabaseState implements DatabaseState<String, byte[]> {
+ private Long nextVersion;
+ private Map<String, AtomicLong> counters;
+ private Map<String, Map<String, Versioned<byte[]>>> maps;
+ private Map<String, Queue<byte[]>> queues;
+
+ /**
+ * This locks map has a structure similar to the "tables" map above and
+ * holds all the provisional updates made during a transaction's prepare phase.
+ * The entry value is represented as the tuple: (transactionId, newValue)
+ * If newValue == null that signifies this update is attempting to
+ * delete the existing value.
+ * This map also serves as a lock on the entries that are being updated.
+ * The presence of a entry in this map indicates that element is
+ * participating in a transaction and is currently locked for updates.
+ */
+ private Map<String, Map<String, Update>> locks;
+
+ @Initializer
+ @Override
+ public void init(StateContext<DatabaseState<String, byte[]>> context) {
+ counters = context.get("counters");
+ if (counters == null) {
+ counters = Maps.newConcurrentMap();
+ context.put("counters", counters);
+ }
+ maps = context.get("maps");
+ if (maps == null) {
+ maps = Maps.newConcurrentMap();
+ context.put("maps", maps);
+ }
+ locks = context.get("locks");
+ if (locks == null) {
+ locks = Maps.newConcurrentMap();
+ context.put("locks", locks);
+ }
+ queues = context.get("queues");
+ if (queues == null) {
+ queues = Maps.newConcurrentMap();
+ context.put("queues", queues);
+ }
+ nextVersion = context.get("nextVersion");
+ if (nextVersion == null) {
+ nextVersion = new Long(0);
+ context.put("nextVersion", nextVersion);
+ }
+ }
+
+ @Override
+ public Set<String> maps() {
+ return ImmutableSet.copyOf(maps.keySet());
+ }
+
+ @Override
+ public Map<String, Long> counters() {
+ Map<String, Long> counterMap = Maps.newHashMap();
+ counters.forEach((k, v) -> counterMap.put(k, v.get()));
+ return counterMap;
+ }
+
+ @Override
+ public int mapSize(String mapName) {
+ return getMap(mapName).size();
+ }
+
+ @Override
+ public boolean mapIsEmpty(String mapName) {
+ return getMap(mapName).isEmpty();
+ }
+
+ @Override
+ public boolean mapContainsKey(String mapName, String key) {
+ return getMap(mapName).containsKey(key);
+ }
+
+ @Override
+ public boolean mapContainsValue(String mapName, byte[] value) {
+ return getMap(mapName).values().stream().anyMatch(v -> Arrays.equals(v.value(), value));
+ }
+
+ @Override
+ public Versioned<byte[]> mapGet(String mapName, String key) {
+ return getMap(mapName).get(key);
+ }
+
+
+ @Override
+ public Result<UpdateResult<String, byte[]>> mapUpdate(
+ String mapName,
+ String key,
+ Match<byte[]> valueMatch,
+ Match<Long> versionMatch,
+ byte[] value) {
+ if (isLockedForUpdates(mapName, key)) {
+ return Result.locked();
+ }
+ Versioned<byte[]> currentValue = getMap(mapName).get(key);
+ if (!valueMatch.matches(currentValue == null ? null : currentValue.value()) ||
+ !versionMatch.matches(currentValue == null ? null : currentValue.version())) {
+ return Result.ok(new UpdateResult<>(false, mapName, key, currentValue, currentValue));
+ } else {
+ if (value == null) {
+ if (currentValue == null) {
+ return Result.ok(new UpdateResult<>(false, mapName, key, null, null));
+ } else {
+ getMap(mapName).remove(key);
+ return Result.ok(new UpdateResult<>(true, mapName, key, currentValue, null));
+ }
+ }
+ Versioned<byte[]> newValue = new Versioned<>(value, ++nextVersion);
+ getMap(mapName).put(key, newValue);
+ return Result.ok(new UpdateResult<>(true, mapName, key, currentValue, newValue));
+ }
+ }
+
+ @Override
+ public Result<Void> mapClear(String mapName) {
+ if (areTransactionsInProgress(mapName)) {
+ return Result.locked();
+ }
+ getMap(mapName).clear();
+ return Result.ok(null);
+ }
+
+ @Override
+ public Set<String> mapKeySet(String mapName) {
+ return ImmutableSet.copyOf(getMap(mapName).keySet());
+ }
+
+ @Override
+ public Collection<Versioned<byte[]>> mapValues(String mapName) {
+ return ImmutableList.copyOf(getMap(mapName).values());
+ }
+
+ @Override
+ public Set<Entry<String, Versioned<byte[]>>> mapEntrySet(String mapName) {
+ return ImmutableSet.copyOf(getMap(mapName)
+ .entrySet()
+ .stream()
+ .map(entry -> Maps.immutableEntry(entry.getKey(), entry.getValue()))
+ .collect(Collectors.toSet()));
+ }
+
+ @Override
+ public Long counterAddAndGet(String counterName, long delta) {
+ return getCounter(counterName).addAndGet(delta);
+ }
+
+ @Override
+ public Long counterGetAndAdd(String counterName, long delta) {
+ return getCounter(counterName).getAndAdd(delta);
+ }
+
+ @Override
+ public Long counterGet(String counterName) {
+ return getCounter(counterName).get();
+ }
+
+ @Override
+ public Long queueSize(String queueName) {
+ return Long.valueOf(getQueue(queueName).size());
+ }
+
+ @Override
+ public byte[] queuePeek(String queueName) {
+ return getQueue(queueName).peek();
+ }
+
+ @Override
+ public byte[] queuePop(String queueName) {
+ return getQueue(queueName).poll();
+ }
+
+ @Override
+ public void queuePush(String queueName, byte[] entry) {
+ getQueue(queueName).offer(entry);
+ }
+
+ @Override
+ public CommitResponse prepareAndCommit(Transaction transaction) {
+ if (prepare(transaction)) {
+ return commit(transaction);
+ }
+ return CommitResponse.failure();
+ }
+
+ @Override
+ public boolean prepare(Transaction transaction) {
+ if (transaction.updates().stream().anyMatch(update ->
+ isLockedByAnotherTransaction(update.mapName(),
+ update.key(),
+ transaction.id()))) {
+ return false;
+ }
+
+ if (transaction.updates().stream().allMatch(this::isUpdatePossible)) {
+ transaction.updates().forEach(update -> doProvisionalUpdate(update, transaction.id()));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public CommitResponse commit(Transaction transaction) {
+ return CommitResponse.success(Lists.transform(transaction.updates(),
+ update -> commitProvisionalUpdate(update, transaction.id())));
+ }
+
+ @Override
+ public boolean rollback(Transaction transaction) {
+ transaction.updates().forEach(update -> undoProvisionalUpdate(update, transaction.id()));
+ return true;
+ }
+
+ private Map<String, Versioned<byte[]>> getMap(String mapName) {
+ return maps.computeIfAbsent(mapName, name -> Maps.newConcurrentMap());
+ }
+
+ private Map<String, Update> getLockMap(String mapName) {
+ return locks.computeIfAbsent(mapName, name -> Maps.newConcurrentMap());
+ }
+
+ private AtomicLong getCounter(String counterName) {
+ return counters.computeIfAbsent(counterName, name -> new AtomicLong(0));
+ }
+
+ private Queue<byte[]> getQueue(String queueName) {
+ return queues.computeIfAbsent(queueName, name -> new LinkedList<>());
+ }
+
+ private boolean isUpdatePossible(DatabaseUpdate update) {
+ Versioned<byte[]> existingEntry = mapGet(update.mapName(), update.key());
+ switch (update.type()) {
+ case PUT:
+ case REMOVE:
+ return true;
+ case PUT_IF_ABSENT:
+ return existingEntry == null;
+ case PUT_IF_VERSION_MATCH:
+ return existingEntry != null && existingEntry.version() == update.currentVersion();
+ case PUT_IF_VALUE_MATCH:
+ return existingEntry != null && Arrays.equals(existingEntry.value(), update.currentValue());
+ case REMOVE_IF_VERSION_MATCH:
+ return existingEntry == null || existingEntry.version() == update.currentVersion();
+ case REMOVE_IF_VALUE_MATCH:
+ return existingEntry == null || Arrays.equals(existingEntry.value(), update.currentValue());
+ default:
+ throw new IllegalStateException("Unsupported type: " + update.type());
+ }
+ }
+
+ private void doProvisionalUpdate(DatabaseUpdate update, long transactionId) {
+ Map<String, Update> lockMap = getLockMap(update.mapName());
+ switch (update.type()) {
+ case PUT:
+ case PUT_IF_ABSENT:
+ case PUT_IF_VERSION_MATCH:
+ case PUT_IF_VALUE_MATCH:
+ lockMap.put(update.key(), new Update(transactionId, update.value()));
+ break;
+ case REMOVE:
+ case REMOVE_IF_VERSION_MATCH:
+ case REMOVE_IF_VALUE_MATCH:
+ lockMap.put(update.key(), new Update(transactionId, null));
+ break;
+ default:
+ throw new IllegalStateException("Unsupported type: " + update.type());
+ }
+ }
+
+ private UpdateResult<String, byte[]> commitProvisionalUpdate(DatabaseUpdate update, long transactionId) {
+ String mapName = update.mapName();
+ String key = update.key();
+ Update provisionalUpdate = getLockMap(mapName).get(key);
+ if (Objects.equal(transactionId, provisionalUpdate.transactionId())) {
+ getLockMap(mapName).remove(key);
+ } else {
+ throw new IllegalStateException("Invalid transaction Id");
+ }
+ return mapUpdate(mapName, key, Match.any(), Match.any(), provisionalUpdate.value()).value();
+ }
+
+ private void undoProvisionalUpdate(DatabaseUpdate update, long transactionId) {
+ String mapName = update.mapName();
+ String key = update.key();
+ Update provisionalUpdate = getLockMap(mapName).get(key);
+ if (provisionalUpdate == null) {
+ return;
+ }
+ if (Objects.equal(transactionId, provisionalUpdate.transactionId())) {
+ getLockMap(mapName).remove(key);
+ }
+ }
+
+ private boolean isLockedByAnotherTransaction(String mapName, String key, long transactionId) {
+ Update update = getLockMap(mapName).get(key);
+ return update != null && !Objects.equal(transactionId, update.transactionId());
+ }
+
+ private boolean isLockedForUpdates(String mapName, String key) {
+ return getLockMap(mapName).containsKey(key);
+ }
+
+ private boolean areTransactionsInProgress(String mapName) {
+ return !getLockMap(mapName).isEmpty();
+ }
+
+ private class Update {
+ private final long transactionId;
+ private final byte[] value;
+
+ public Update(long txId, byte[] value) {
+ this.transactionId = txId;
+ this.value = value;
+ }
+
+ public long transactionId() {
+ return this.transactionId;
+ }
+
+ public byte[] value() {
+ return this.value;
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueue.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueue.java
new file mode 100644
index 00000000..5f69fde8
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueue.java
@@ -0,0 +1,129 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+
+import org.onlab.util.SharedExecutors;
+import org.onosproject.store.service.DistributedQueue;
+import org.onosproject.store.service.Serializer;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.store.consistent.impl.StateMachineUpdate.Target.QUEUE_PUSH;
+
+/**
+ * DistributedQueue implementation that provides FIFO ordering semantics.
+ *
+ * @param <E> queue entry type
+ */
+public class DefaultDistributedQueue<E> implements DistributedQueue<E> {
+
+ private final String name;
+ private final Database database;
+ private final Serializer serializer;
+ private final Set<CompletableFuture<E>> pendingFutures = Sets.newIdentityHashSet();
+
+ private static final String PRIMITIVE_NAME = "distributedQueue";
+ private static final String SIZE = "size";
+ private static final String PUSH = "push";
+ private static final String POP = "pop";
+ private static final String PEEK = "peek";
+
+ private static final String ERROR_NULL_ENTRY = "Null entries are not allowed";
+ private final MeteringAgent monitor;
+
+ public DefaultDistributedQueue(String name,
+ Database database,
+ Serializer serializer,
+ boolean meteringEnabled) {
+ this.name = checkNotNull(name, "queue name cannot be null");
+ this.database = checkNotNull(database, "database cannot be null");
+ this.serializer = checkNotNull(serializer, "serializer cannot be null");
+ this.monitor = new MeteringAgent(PRIMITIVE_NAME, name, meteringEnabled);
+ this.database.registerConsumer(update -> {
+ SharedExecutors.getSingleThreadExecutor().execute(() -> {
+ if (update.target() == QUEUE_PUSH) {
+ List<Object> input = update.input();
+ String queueName = (String) input.get(0);
+ if (queueName.equals(name)) {
+ tryPoll();
+ }
+ }
+ });
+ });
+ }
+
+ @Override
+ public long size() {
+ final MeteringAgent.Context timer = monitor.startTimer(SIZE);
+ return Futures.getUnchecked(database.queueSize(name).whenComplete((r, e) -> timer.stop(e)));
+ }
+
+ @Override
+ public void push(E entry) {
+ checkNotNull(entry, ERROR_NULL_ENTRY);
+ final MeteringAgent.Context timer = monitor.startTimer(PUSH);
+ Futures.getUnchecked(database.queuePush(name, serializer.encode(entry))
+ .whenComplete((r, e) -> timer.stop(e)));
+ }
+
+ @Override
+ public CompletableFuture<E> pop() {
+ final MeteringAgent.Context timer = monitor.startTimer(POP);
+ return database.queuePop(name)
+ .whenComplete((r, e) -> timer.stop(e))
+ .thenCompose(v -> {
+ if (v != null) {
+ return CompletableFuture.<E>completedFuture(serializer.decode(v));
+ }
+ CompletableFuture<E> newPendingFuture = new CompletableFuture<>();
+ pendingFutures.add(newPendingFuture);
+ return newPendingFuture;
+ });
+
+ }
+
+ @Override
+ public E peek() {
+ final MeteringAgent.Context timer = monitor.startTimer(PEEK);
+ return Futures.getUnchecked(database.queuePeek(name)
+ .thenApply(v -> v != null ? serializer.<E>decode(v) : null)
+ .whenComplete((r, e) -> timer.stop(e)));
+ }
+
+ public String name() {
+ return name;
+ }
+
+ protected void tryPoll() {
+ Set<CompletableFuture<E>> completedFutures = Sets.newHashSet();
+ for (CompletableFuture<E> future : pendingFutures) {
+ E entry = Futures.getUnchecked(database.queuePop(name)
+ .thenApply(v -> v != null ? serializer.decode(v) : null));
+ if (entry != null) {
+ future.complete(entry);
+ completedFutures.add(future);
+ } else {
+ break;
+ }
+ }
+ pendingFutures.removeAll(completedFutures);
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueueBuilder.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueueBuilder.java
new file mode 100644
index 00000000..d6654e27
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedQueueBuilder.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.store.consistent.impl;
+
+import org.onosproject.store.service.DistributedQueue;
+import org.onosproject.store.service.DistributedQueueBuilder;
+import org.onosproject.store.service.Serializer;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Default implementation of a {@code DistributedQueueBuilder}.
+ *
+ * @param <E> queue entry type
+ */
+public class DefaultDistributedQueueBuilder<E> implements DistributedQueueBuilder<E> {
+
+ private Serializer serializer;
+ private String name;
+ private boolean persistenceEnabled = true;
+ private final DatabaseManager databaseManager;
+ private boolean metering = true;
+
+ public DefaultDistributedQueueBuilder(DatabaseManager databaseManager) {
+ this.databaseManager = databaseManager;
+ }
+
+ @Override
+ public DistributedQueueBuilder<E> withName(String name) {
+ checkArgument(name != null && !name.isEmpty());
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public DistributedQueueBuilder<E> withSerializer(Serializer serializer) {
+ checkArgument(serializer != null);
+ this.serializer = serializer;
+ return this;
+ }
+
+ @Override
+ public DistributedQueueBuilder<E> withMeteringDisabled() {
+ metering = false;
+ return this;
+ }
+
+ @Override
+ public DistributedQueueBuilder<E> withPersistenceDisabled() {
+ persistenceEnabled = false;
+ return this;
+ }
+
+ private boolean validInputs() {
+ return name != null && serializer != null;
+ }
+
+ @Override
+ public DistributedQueue<E> build() {
+ checkState(validInputs());
+ return new DefaultDistributedQueue<>(
+ name,
+ persistenceEnabled ? databaseManager.partitionedDatabase : databaseManager.inMemoryDatabase,
+ serializer,
+ metering);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSet.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSet.java
new file mode 100644
index 00000000..677724df
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSet.java
@@ -0,0 +1,234 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.SetEvent;
+import org.onosproject.store.service.SetEventListener;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of distributed set that is backed by a ConsistentMap.
+
+ * @param <E> set element type
+ */
+public class DefaultDistributedSet<E> implements DistributedSet<E> {
+
+ private static final String CONTAINS = "contains";
+ private static final String PRIMITIVE_NAME = "distributedSet";
+ private static final String SIZE = "size";
+ private static final String IS_EMPTY = "isEmpty";
+ private static final String ITERATOR = "iterator";
+ private static final String TO_ARRAY = "toArray";
+ private static final String ADD = "add";
+ private static final String REMOVE = "remove";
+ private static final String CONTAINS_ALL = "containsAll";
+ private static final String ADD_ALL = "addAll";
+ private static final String RETAIN_ALL = "retainAll";
+ private static final String REMOVE_ALL = "removeAll";
+ private static final String CLEAR = "clear";
+
+ private final String name;
+ private final ConsistentMap<E, Boolean> backingMap;
+ private final Map<SetEventListener<E>, MapEventListener<E, Boolean>> listenerMapping = Maps.newIdentityHashMap();
+ private final MeteringAgent monitor;
+
+ public DefaultDistributedSet(String name, boolean meteringEnabled, ConsistentMap<E, Boolean> backingMap) {
+ this.name = name;
+ this.backingMap = backingMap;
+ monitor = new MeteringAgent(PRIMITIVE_NAME, name, meteringEnabled);
+ }
+
+ @Override
+ public int size() {
+ final MeteringAgent.Context timer = monitor.startTimer(SIZE);
+ try {
+ return backingMap.size();
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ final MeteringAgent.Context timer = monitor.startTimer(IS_EMPTY);
+ try {
+ return backingMap.isEmpty();
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean contains(Object o) {
+ final MeteringAgent.Context timer = monitor.startTimer(CONTAINS);
+ try {
+ return backingMap.containsKey((E) o);
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ final MeteringAgent.Context timer = monitor.startTimer(ITERATOR);
+ //Do we have to measure this guy?
+ try {
+ return backingMap.keySet().iterator();
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public Object[] toArray() {
+ final MeteringAgent.Context timer = monitor.startTimer(TO_ARRAY);
+ try {
+ return backingMap.keySet().stream().toArray();
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ final MeteringAgent.Context timer = monitor.startTimer(TO_ARRAY);
+ try {
+ return backingMap.keySet().stream().toArray(size -> a);
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public boolean add(E e) {
+ final MeteringAgent.Context timer = monitor.startTimer(ADD);
+ try {
+ return backingMap.putIfAbsent(e, true) == null;
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean remove(Object o) {
+ final MeteringAgent.Context timer = monitor.startTimer(REMOVE);
+ try {
+ return backingMap.remove((E) o) != null;
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_ALL);
+ try {
+ return c.stream()
+ .allMatch(this::contains);
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ final MeteringAgent.Context timer = monitor.startTimer(ADD_ALL);
+ try {
+ return c.stream()
+ .map(this::add)
+ .reduce(Boolean::logicalOr)
+ .orElse(false);
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ final MeteringAgent.Context timer = monitor.startTimer(RETAIN_ALL);
+ try {
+ Set<?> retainSet = Sets.newHashSet(c);
+ return backingMap.keySet()
+ .stream()
+ .filter(k -> !retainSet.contains(k))
+ .map(this::remove)
+ .reduce(Boolean::logicalOr)
+ .orElse(false);
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ final MeteringAgent.Context timer = monitor.startTimer(REMOVE_ALL);
+ try {
+ Set<?> removeSet = Sets.newHashSet(c);
+ return backingMap.keySet()
+ .stream()
+ .filter(removeSet::contains)
+ .map(this::remove)
+ .reduce(Boolean::logicalOr)
+ .orElse(false);
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public void clear() {
+ final MeteringAgent.Context timer = monitor.startTimer(CLEAR);
+ try {
+ backingMap.clear();
+ } finally {
+ timer.stop(null);
+ }
+ }
+
+ @Override
+ public void addListener(SetEventListener<E> listener) {
+ MapEventListener<E, Boolean> mapEventListener = mapEvent -> {
+ if (mapEvent.type() == MapEvent.Type.INSERT) {
+ listener.event(new SetEvent<>(name, SetEvent.Type.ADD, mapEvent.key()));
+ } else if (mapEvent.type() == MapEvent.Type.REMOVE) {
+ listener.event(new SetEvent<>(name, SetEvent.Type.REMOVE, mapEvent.key()));
+ }
+ };
+ if (listenerMapping.putIfAbsent(listener, mapEventListener) == null) {
+ backingMap.addListener(mapEventListener);
+ }
+ }
+
+ @Override
+ public void removeListener(SetEventListener<E> listener) {
+ MapEventListener<E, Boolean> mapEventListener = listenerMapping.remove(listener);
+ if (mapEventListener != null) {
+ backingMap.removeListener(mapEventListener);
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSetBuilder.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSetBuilder.java
new file mode 100644
index 00000000..f7957f39
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultDistributedSetBuilder.java
@@ -0,0 +1,93 @@
+/*
+ * 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.store.consistent.impl;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.DistributedSetBuilder;
+
+/**
+ * Default distributed set builder.
+ *
+ * @param <E> type for set elements
+ */
+public class DefaultDistributedSetBuilder<E> implements DistributedSetBuilder<E> {
+
+ private String name;
+ private ConsistentMapBuilder<E, Boolean> mapBuilder;
+ private boolean metering = true;
+
+ public DefaultDistributedSetBuilder(DatabaseManager manager) {
+ this.mapBuilder = manager.consistentMapBuilder();
+ mapBuilder.withMeteringDisabled();
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withName(String name) {
+ mapBuilder.withName(name);
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withApplicationId(ApplicationId id) {
+ mapBuilder.withApplicationId(id);
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withPurgeOnUninstall() {
+ mapBuilder.withPurgeOnUninstall();
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withSerializer(Serializer serializer) {
+ mapBuilder.withSerializer(serializer);
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withUpdatesDisabled() {
+ mapBuilder.withUpdatesDisabled();
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withRelaxedReadConsistency() {
+ mapBuilder.withRelaxedReadConsistency();
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withPartitionsDisabled() {
+ mapBuilder.withPartitionsDisabled();
+ return this;
+ }
+
+ @Override
+ public DistributedSetBuilder<E> withMeteringDisabled() {
+ metering = false;
+ return this;
+ }
+
+ @Override
+ public DistributedSet<E> build() {
+ return new DefaultDistributedSet<E>(name, metering, mapBuilder.build());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransaction.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransaction.java
new file mode 100644
index 00000000..2ff7a2dc
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransaction.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.store.consistent.impl;
+
+import java.util.List;
+
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Transaction;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A Default transaction implementation.
+ */
+public class DefaultTransaction implements Transaction {
+
+ private final long transactionId;
+ private final List<DatabaseUpdate> updates;
+ private final State state;
+ private final long lastUpdated;
+
+ public DefaultTransaction(long transactionId, List<DatabaseUpdate> updates) {
+ this(transactionId, updates, State.PREPARING, System.currentTimeMillis());
+ }
+
+ private DefaultTransaction(long transactionId, List<DatabaseUpdate> updates, State state, long lastUpdated) {
+ this.transactionId = transactionId;
+ this.updates = ImmutableList.copyOf(updates);
+ this.state = state;
+ this.lastUpdated = lastUpdated;
+ }
+
+ @Override
+ public long id() {
+ return transactionId;
+ }
+
+ @Override
+ public List<DatabaseUpdate> updates() {
+ return updates;
+ }
+
+ @Override
+ public State state() {
+ return state;
+ }
+
+ @Override
+ public Transaction transition(State newState) {
+ return new DefaultTransaction(transactionId, updates, newState, System.currentTimeMillis());
+ }
+
+ @Override
+ public long lastUpdated() {
+ return lastUpdated;
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java
new file mode 100644
index 00000000..b66f424b
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.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.store.consistent.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static com.google.common.base.Preconditions.*;
+
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.TransactionContext;
+import org.onosproject.store.service.TransactionalMap;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+
+/**
+ * Default TransactionContext implementation.
+ */
+public class DefaultTransactionContext implements TransactionContext {
+ private static final String TX_NOT_OPEN_ERROR = "Transaction Context is not open";
+
+ @SuppressWarnings("rawtypes")
+ private final Map<String, DefaultTransactionalMap> txMaps = Maps.newConcurrentMap();
+ private boolean isOpen = false;
+ private final Database database;
+ private final long transactionId;
+ private final Supplier<ConsistentMapBuilder> mapBuilderSupplier;
+
+ public DefaultTransactionContext(long transactionId,
+ Database database,
+ Supplier<ConsistentMapBuilder> mapBuilderSupplier) {
+ this.transactionId = transactionId;
+ this.database = checkNotNull(database);
+ this.mapBuilderSupplier = checkNotNull(mapBuilderSupplier);
+ }
+
+ @Override
+ public long transactionId() {
+ return transactionId;
+ }
+
+ @Override
+ public void begin() {
+ checkState(!isOpen, "Transaction Context is already open");
+ isOpen = true;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return isOpen;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <K, V> TransactionalMap<K, V> getTransactionalMap(String mapName,
+ Serializer serializer) {
+ checkState(isOpen, TX_NOT_OPEN_ERROR);
+ checkNotNull(mapName);
+ checkNotNull(serializer);
+ return txMaps.computeIfAbsent(mapName, name -> new DefaultTransactionalMap<>(
+ name,
+ mapBuilderSupplier.get().withName(name).withSerializer(serializer).build(),
+ this,
+ serializer));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void commit() {
+ // TODO: rework commit implementation to be more intuitive
+ checkState(isOpen, TX_NOT_OPEN_ERROR);
+ CommitResponse response = null;
+ try {
+ List<DatabaseUpdate> updates = Lists.newLinkedList();
+ txMaps.values().forEach(m -> updates.addAll(m.prepareDatabaseUpdates()));
+ Transaction transaction = new DefaultTransaction(transactionId, updates);
+ response = Futures.getUnchecked(database.prepareAndCommit(transaction));
+ } finally {
+ if (response != null && !response.success()) {
+ abort();
+ }
+ isOpen = false;
+ }
+ }
+
+ @Override
+ public void abort() {
+ if (isOpen) {
+ try {
+ txMaps.values().forEach(m -> m.rollback());
+ } finally {
+ isOpen = false;
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContextBuilder.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContextBuilder.java
new file mode 100644
index 00000000..f20bfb80
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContextBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * 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.store.consistent.impl;
+
+import org.onosproject.store.service.TransactionContext;
+import org.onosproject.store.service.TransactionContextBuilder;
+
+/**
+ * The default implementation of a transaction context builder. This builder
+ * generates a {@link DefaultTransactionContext}.
+ */
+public class DefaultTransactionContextBuilder implements TransactionContextBuilder {
+
+ private boolean partitionsEnabled = true;
+ private final DatabaseManager manager;
+ private final long transactionId;
+
+ public DefaultTransactionContextBuilder(DatabaseManager manager, long transactionId) {
+ this.manager = manager;
+ this.transactionId = transactionId;
+ }
+
+ @Override
+ public TransactionContextBuilder withPartitionsDisabled() {
+ partitionsEnabled = false;
+ return this;
+ }
+
+ @Override
+ public TransactionContext build() {
+ return new DefaultTransactionContext(
+ transactionId,
+ partitionsEnabled ? manager.partitionedDatabase : manager.inMemoryDatabase,
+ () -> partitionsEnabled ? manager.consistentMapBuilder()
+ : manager.consistentMapBuilder().withPartitionsDisabled());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionalMap.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionalMap.java
new file mode 100644
index 00000000..ade70335
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionalMap.java
@@ -0,0 +1,204 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.onlab.util.HexString;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.TransactionContext;
+import org.onosproject.store.service.TransactionalMap;
+import org.onosproject.store.service.Versioned;
+
+import static com.google.common.base.Preconditions.*;
+
+import com.google.common.base.Objects;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * Default Transactional Map implementation that provides a repeatable reads
+ * transaction isolation level.
+ *
+ * @param <K> key type
+ * @param <V> value type.
+ */
+public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
+
+ private final TransactionContext txContext;
+ private static final String TX_CLOSED_ERROR = "Transaction is closed";
+ private final ConsistentMap<K, V> backingMap;
+ private final String name;
+ private final Serializer serializer;
+ private final Map<K, Versioned<V>> readCache = Maps.newConcurrentMap();
+ private final Map<K, V> writeCache = Maps.newConcurrentMap();
+ private final Set<K> deleteSet = Sets.newConcurrentHashSet();
+
+ private static final String ERROR_NULL_VALUE = "Null values are not allowed";
+ private static final String ERROR_NULL_KEY = "Null key is not allowed";
+
+ private final LoadingCache<K, String> keyCache = CacheBuilder.newBuilder()
+ .softValues()
+ .build(new CacheLoader<K, String>() {
+
+ @Override
+ public String load(K key) {
+ return HexString.toHexString(serializer.encode(key));
+ }
+ });
+
+ protected K dK(String key) {
+ return serializer.decode(HexString.fromHexString(key));
+ }
+
+ public DefaultTransactionalMap(
+ String name,
+ ConsistentMap<K, V> backingMap,
+ TransactionContext txContext,
+ Serializer serializer) {
+ this.name = name;
+ this.backingMap = backingMap;
+ this.txContext = txContext;
+ this.serializer = serializer;
+ }
+
+ @Override
+ public V get(K key) {
+ checkState(txContext.isOpen(), TX_CLOSED_ERROR);
+ checkNotNull(key, ERROR_NULL_KEY);
+ if (deleteSet.contains(key)) {
+ return null;
+ }
+ V latest = writeCache.get(key);
+ if (latest != null) {
+ return latest;
+ } else {
+ Versioned<V> v = readCache.computeIfAbsent(key, k -> backingMap.get(k));
+ return v != null ? v.value() : null;
+ }
+ }
+
+ @Override
+ public V put(K key, V value) {
+ checkState(txContext.isOpen(), TX_CLOSED_ERROR);
+ checkNotNull(value, ERROR_NULL_VALUE);
+
+ V latest = get(key);
+ writeCache.put(key, value);
+ deleteSet.remove(key);
+ return latest;
+ }
+
+ @Override
+ public V remove(K key) {
+ checkState(txContext.isOpen(), TX_CLOSED_ERROR);
+ V latest = get(key);
+ if (latest != null) {
+ writeCache.remove(key);
+ deleteSet.add(key);
+ }
+ return latest;
+ }
+
+ @Override
+ public boolean remove(K key, V value) {
+ checkState(txContext.isOpen(), TX_CLOSED_ERROR);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ V latest = get(key);
+ if (Objects.equal(value, latest)) {
+ remove(key);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ checkState(txContext.isOpen(), TX_CLOSED_ERROR);
+ checkNotNull(oldValue, ERROR_NULL_VALUE);
+ checkNotNull(newValue, ERROR_NULL_VALUE);
+ V latest = get(key);
+ if (Objects.equal(oldValue, latest)) {
+ put(key, newValue);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ checkState(txContext.isOpen(), TX_CLOSED_ERROR);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ V latest = get(key);
+ if (latest == null) {
+ put(key, value);
+ }
+ return latest;
+ }
+
+ protected List<DatabaseUpdate> prepareDatabaseUpdates() {
+ List<DatabaseUpdate> updates = Lists.newLinkedList();
+ deleteSet.forEach(key -> {
+ Versioned<V> original = readCache.get(key);
+ if (original != null) {
+ updates.add(DatabaseUpdate.newBuilder()
+ .withMapName(name)
+ .withType(DatabaseUpdate.Type.REMOVE_IF_VERSION_MATCH)
+ .withKey(keyCache.getUnchecked(key))
+ .withCurrentVersion(original.version())
+ .build());
+ }
+ });
+ writeCache.forEach((key, value) -> {
+ Versioned<V> original = readCache.get(key);
+ if (original == null) {
+ updates.add(DatabaseUpdate.newBuilder()
+ .withMapName(name)
+ .withType(DatabaseUpdate.Type.PUT_IF_ABSENT)
+ .withKey(keyCache.getUnchecked(key))
+ .withValue(serializer.encode(value))
+ .build());
+ } else {
+ updates.add(DatabaseUpdate.newBuilder()
+ .withMapName(name)
+ .withType(DatabaseUpdate.Type.PUT_IF_VERSION_MATCH)
+ .withKey(keyCache.getUnchecked(key))
+ .withCurrentVersion(original.version())
+ .withValue(serializer.encode(value))
+ .build());
+ }
+ });
+ return updates;
+ }
+
+ /**
+ * Discards all changes made to this transactional map.
+ */
+ protected void rollback() {
+ readCache.clear();
+ writeCache.clear();
+ deleteSet.clear();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DistributedLeadershipManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DistributedLeadershipManager.java
new file mode 100644
index 00000000..1882b1b5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DistributedLeadershipManager.java
@@ -0,0 +1,605 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapDifference;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.lang.math.RandomUtils;
+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.ClusterEvent;
+import org.onosproject.cluster.ClusterEvent.Type;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapException;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
+import static org.onosproject.cluster.ControllerNode.State.INACTIVE;
+
+/**
+ * Distributed Lock Manager implemented on top of ConsistentMap.
+ * <p>
+ * This implementation makes use of ClusterService's failure
+ * detection capabilities to detect and purge stale locks.
+ * TODO: Ensure lock safety and liveness.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class DistributedLeadershipManager implements LeadershipService {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ private final Logger log = getLogger(getClass());
+ private ScheduledExecutorService electionRunner;
+ private ScheduledExecutorService lockExecutor;
+ private ScheduledExecutorService staleLeadershipPurgeExecutor;
+ private ScheduledExecutorService leadershipRefresher;
+
+ private ConsistentMap<String, NodeId> leaderMap;
+ private ConsistentMap<String, List<NodeId>> candidateMap;
+
+ private ListenerRegistry<LeadershipEvent, LeadershipEventListener> listenerRegistry;
+ private final Map<String, Leadership> leaderBoard = Maps.newConcurrentMap();
+ private final Map<String, Leadership> candidateBoard = Maps.newConcurrentMap();
+ private final ClusterEventListener clusterEventListener = new InternalClusterEventListener();
+
+ private NodeId localNodeId;
+ private Set<String> activeTopics = Sets.newConcurrentHashSet();
+ private Map<String, CompletableFuture<Leadership>> pendingFutures = Maps.newConcurrentMap();
+
+ // The actual delay is randomly chosen from the interval [0, WAIT_BEFORE_RETRY_MILLIS)
+ private static final int WAIT_BEFORE_RETRY_MILLIS = 150;
+ private static final int DELAY_BETWEEN_LEADER_LOCK_ATTEMPTS_SEC = 2;
+ private static final int LEADERSHIP_REFRESH_INTERVAL_SEC = 2;
+ private static final int DELAY_BETWEEN_STALE_LEADERSHIP_PURGE_ATTEMPTS_SEC = 2;
+
+ private final AtomicBoolean staleLeadershipPurgeScheduled = new AtomicBoolean(false);
+
+ private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
+
+ @Activate
+ public void activate() {
+ leaderMap = storageService.<String, NodeId>consistentMapBuilder()
+ .withName("onos-topic-leaders")
+ .withSerializer(SERIALIZER)
+ .withPartitionsDisabled().build();
+ candidateMap = storageService.<String, List<NodeId>>consistentMapBuilder()
+ .withName("onos-topic-candidates")
+ .withSerializer(SERIALIZER)
+ .withPartitionsDisabled().build();
+
+ leaderMap.addListener(event -> {
+ log.debug("Received {}", event);
+ LeadershipEvent.Type leadershipEventType = null;
+ if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
+ leadershipEventType = LeadershipEvent.Type.LEADER_ELECTED;
+ } else if (event.type() == MapEvent.Type.REMOVE) {
+ leadershipEventType = LeadershipEvent.Type.LEADER_BOOTED;
+ }
+ onLeadershipEvent(new LeadershipEvent(
+ leadershipEventType,
+ new Leadership(event.key(),
+ event.value().value(),
+ event.value().version(),
+ event.value().creationTime())));
+ });
+
+ candidateMap.addListener(event -> {
+ log.debug("Received {}", event);
+ if (event.type() != MapEvent.Type.INSERT && event.type() != MapEvent.Type.UPDATE) {
+ log.error("Entries must not be removed from candidate map");
+ return;
+ }
+ onLeadershipEvent(new LeadershipEvent(
+ LeadershipEvent.Type.CANDIDATES_CHANGED,
+ new Leadership(event.key(),
+ event.value().value(),
+ event.value().version(),
+ event.value().creationTime())));
+ });
+
+ localNodeId = clusterService.getLocalNode().id();
+
+ electionRunner = Executors.newSingleThreadScheduledExecutor(
+ groupedThreads("onos/store/leadership", "election-runner"));
+ lockExecutor = Executors.newScheduledThreadPool(
+ 4, groupedThreads("onos/store/leadership", "election-thread-%d"));
+ staleLeadershipPurgeExecutor = Executors.newSingleThreadScheduledExecutor(
+ groupedThreads("onos/store/leadership", "stale-leadership-evictor"));
+ leadershipRefresher = Executors.newSingleThreadScheduledExecutor(
+ groupedThreads("onos/store/leadership", "refresh-thread"));
+
+ clusterService.addListener(clusterEventListener);
+
+ electionRunner.scheduleWithFixedDelay(
+ this::electLeaders, 0, DELAY_BETWEEN_LEADER_LOCK_ATTEMPTS_SEC, TimeUnit.SECONDS);
+
+ leadershipRefresher.scheduleWithFixedDelay(
+ this::refreshLeaderBoard, 0, LEADERSHIP_REFRESH_INTERVAL_SEC, TimeUnit.SECONDS);
+
+ listenerRegistry = new ListenerRegistry<>();
+ eventDispatcher.addSink(LeadershipEvent.class, listenerRegistry);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ if (clusterService.getNodes().size() > 1) {
+ // FIXME: Determine why this takes ~50 seconds to shutdown on a single node!
+ leaderBoard.forEach((topic, leadership) -> {
+ if (localNodeId.equals(leadership.leader())) {
+ withdraw(topic);
+ }
+ });
+ }
+
+ clusterService.removeListener(clusterEventListener);
+ eventDispatcher.removeSink(LeadershipEvent.class);
+
+ electionRunner.shutdown();
+ lockExecutor.shutdown();
+ staleLeadershipPurgeExecutor.shutdown();
+ leadershipRefresher.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public Map<String, Leadership> getLeaderBoard() {
+ return ImmutableMap.copyOf(leaderBoard);
+ }
+
+ @Override
+ public Map<String, List<NodeId>> getCandidates() {
+ return Maps.toMap(candidateBoard.keySet(), this::getCandidates);
+ }
+
+ @Override
+ public List<NodeId> getCandidates(String path) {
+ Leadership current = candidateBoard.get(path);
+ return current == null ? ImmutableList.of() : ImmutableList.copyOf(current.candidates());
+ }
+
+ @Override
+ public NodeId getLeader(String path) {
+ Leadership leadership = leaderBoard.get(path);
+ return leadership != null ? leadership.leader() : null;
+ }
+
+ @Override
+ public Leadership getLeadership(String path) {
+ checkArgument(path != null);
+ return leaderBoard.get(path);
+ }
+
+ @Override
+ public Set<String> ownedTopics(NodeId nodeId) {
+ checkArgument(nodeId != null);
+ return leaderBoard.entrySet()
+ .stream()
+ .filter(entry -> nodeId.equals(entry.getValue().leader()))
+ .map(Entry::getKey)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public CompletableFuture<Leadership> runForLeadership(String path) {
+ log.debug("Running for leadership for topic: {}", path);
+ CompletableFuture<Leadership> resultFuture = new CompletableFuture<>();
+ doRunForLeadership(path, resultFuture);
+ return resultFuture;
+ }
+
+ private void doRunForLeadership(String path, CompletableFuture<Leadership> future) {
+ try {
+ Versioned<List<NodeId>> candidates = candidateMap.computeIf(path,
+ currentList -> currentList == null || !currentList.contains(localNodeId),
+ (topic, currentList) -> {
+ if (currentList == null) {
+ return ImmutableList.of(localNodeId);
+ } else {
+ List<NodeId> newList = Lists.newLinkedList();
+ newList.addAll(currentList);
+ newList.add(localNodeId);
+ return newList;
+ }
+ });
+ log.debug("In the leadership race for topic {} with candidates {}", path, candidates);
+ activeTopics.add(path);
+ Leadership leadership = electLeader(path, candidates.value());
+ if (leadership == null) {
+ pendingFutures.put(path, future);
+ } else {
+ future.complete(leadership);
+ }
+ } catch (ConsistentMapException e) {
+ log.debug("Failed to enter topic leader race for {}. Retrying.", path, e);
+ rerunForLeadership(path, future);
+ }
+ }
+
+ @Override
+ public CompletableFuture<Void> withdraw(String path) {
+ activeTopics.remove(path);
+ CompletableFuture<Void> resultFuture = new CompletableFuture<>();
+ doWithdraw(path, resultFuture);
+ return resultFuture;
+ }
+
+
+ private void doWithdraw(String path, CompletableFuture<Void> future) {
+ if (activeTopics.contains(path)) {
+ future.completeExceptionally(new CancellationException(String.format("%s is now a active topic", path)));
+ }
+ try {
+ leaderMap.computeIf(path,
+ localNodeId::equals,
+ (topic, leader) -> null);
+ candidateMap.computeIf(path,
+ candidates -> candidates != null && candidates.contains(localNodeId),
+ (topic, candidates) -> candidates.stream()
+ .filter(nodeId -> !localNodeId.equals(nodeId))
+ .collect(Collectors.toList()));
+ future.complete(null);
+ } catch (Exception e) {
+ log.debug("Failed to verify (and clear) any lock this node might be holding for {}", path, e);
+ retryWithdraw(path, future);
+ }
+ }
+
+ @Override
+ public boolean stepdown(String path) {
+ if (!activeTopics.contains(path) || !Objects.equals(localNodeId, getLeader(path))) {
+ return false;
+ }
+
+ try {
+ return leaderMap.computeIf(path,
+ localNodeId::equals,
+ (topic, leader) -> null) == null;
+ } catch (Exception e) {
+ log.warn("Error executing stepdown for {}", path, e);
+ }
+ return false;
+ }
+
+ @Override
+ public void addListener(LeadershipEventListener listener) {
+ listenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(LeadershipEventListener listener) {
+ listenerRegistry.removeListener(listener);
+ }
+
+ @Override
+ public boolean makeTopCandidate(String path, NodeId nodeId) {
+ Versioned<List<NodeId>> candidateList = candidateMap.computeIf(path,
+ candidates -> candidates != null &&
+ candidates.contains(nodeId) &&
+ !nodeId.equals(Iterables.getFirst(candidates, null)),
+ (topic, candidates) -> {
+ List<NodeId> updatedCandidates = new ArrayList<>(candidates.size());
+ updatedCandidates.add(nodeId);
+ candidates.stream().filter(id -> !nodeId.equals(id)).forEach(updatedCandidates::add);
+ return updatedCandidates;
+ });
+ List<NodeId> candidates = candidateList != null ? candidateList.value() : Collections.emptyList();
+ return candidates.size() > 0 && nodeId.equals(candidates.get(0));
+ }
+
+ private Leadership electLeader(String path, List<NodeId> candidates) {
+ Leadership currentLeadership = getLeadership(path);
+ if (currentLeadership != null) {
+ return currentLeadership;
+ } else {
+ NodeId topCandidate = candidates
+ .stream()
+ .filter(n -> clusterService.getState(n) == ACTIVE)
+ .findFirst()
+ .orElse(null);
+ try {
+ Versioned<NodeId> leader = localNodeId.equals(topCandidate)
+ ? leaderMap.computeIfAbsent(path, p -> localNodeId) : leaderMap.get(path);
+ if (leader != null) {
+ Leadership newLeadership = new Leadership(path,
+ leader.value(),
+ leader.version(),
+ leader.creationTime());
+ // Since reads only go through the local copy of leader board, we ought to update it
+ // first before returning from this method.
+ // This is to ensure a subsequent read will not read a stale value.
+ onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, newLeadership));
+ return newLeadership;
+ }
+ } catch (Exception e) {
+ log.debug("Failed to elect leader for {}", path, e);
+ }
+ }
+ return null;
+ }
+
+ private void electLeaders() {
+ try {
+ candidateMap.entrySet().forEach(entry -> {
+ String path = entry.getKey();
+ Versioned<List<NodeId>> candidates = entry.getValue();
+ // for active topics, check if this node can become a leader (if it isn't already)
+ if (activeTopics.contains(path)) {
+ lockExecutor.submit(() -> {
+ Leadership leadership = electLeader(path, candidates.value());
+ if (leadership != null) {
+ CompletableFuture<Leadership> future = pendingFutures.remove(path);
+ if (future != null) {
+ future.complete(leadership);
+ }
+ }
+ });
+ }
+ // Raise a CANDIDATES_CHANGED event to force refresh local candidate board
+ // and also to update local listeners.
+ // Don't worry about duplicate events as they will be suppressed.
+ onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
+ new Leadership(path,
+ candidates.value(),
+ candidates.version(),
+ candidates.creationTime())));
+ });
+ } catch (Exception e) {
+ log.debug("Failure electing leaders", e);
+ }
+ }
+
+ private void onLeadershipEvent(LeadershipEvent leadershipEvent) {
+ log.trace("Leadership Event: time = {} type = {} event = {}",
+ leadershipEvent.time(), leadershipEvent.type(),
+ leadershipEvent);
+
+ Leadership leadershipUpdate = leadershipEvent.subject();
+ LeadershipEvent.Type eventType = leadershipEvent.type();
+ String topic = leadershipUpdate.topic();
+
+ AtomicBoolean updateAccepted = new AtomicBoolean(false);
+ if (eventType.equals(LeadershipEvent.Type.LEADER_ELECTED)) {
+ leaderBoard.compute(topic, (k, currentLeadership) -> {
+ if (currentLeadership == null || currentLeadership.epoch() < leadershipUpdate.epoch()) {
+ updateAccepted.set(true);
+ return leadershipUpdate;
+ }
+ return currentLeadership;
+ });
+ } else if (eventType.equals(LeadershipEvent.Type.LEADER_BOOTED)) {
+ leaderBoard.compute(topic, (k, currentLeadership) -> {
+ if (currentLeadership == null || currentLeadership.epoch() <= leadershipUpdate.epoch()) {
+ updateAccepted.set(true);
+ // FIXME: Removing entries from leaderboard is not safe and should be visited.
+ return null;
+ }
+ return currentLeadership;
+ });
+ } else if (eventType.equals(LeadershipEvent.Type.CANDIDATES_CHANGED)) {
+ candidateBoard.compute(topic, (k, currentInfo) -> {
+ if (currentInfo == null || currentInfo.epoch() < leadershipUpdate.epoch()) {
+ updateAccepted.set(true);
+ return leadershipUpdate;
+ }
+ return currentInfo;
+ });
+ } else {
+ throw new IllegalStateException("Unknown event type.");
+ }
+
+ if (updateAccepted.get()) {
+ eventDispatcher.post(leadershipEvent);
+ }
+ }
+
+ private void rerunForLeadership(String path, CompletableFuture<Leadership> future) {
+ lockExecutor.schedule(
+ () -> doRunForLeadership(path, future),
+ RandomUtils.nextInt(WAIT_BEFORE_RETRY_MILLIS),
+ TimeUnit.MILLISECONDS);
+ }
+
+ private void retryWithdraw(String path, CompletableFuture<Void> future) {
+ lockExecutor.schedule(
+ () -> doWithdraw(path, future),
+ RandomUtils.nextInt(WAIT_BEFORE_RETRY_MILLIS),
+ TimeUnit.MILLISECONDS);
+ }
+
+ private void scheduleStaleLeadershipPurge(int afterDelaySec) {
+ if (staleLeadershipPurgeScheduled.compareAndSet(false, true)) {
+ staleLeadershipPurgeExecutor.schedule(
+ this::purgeStaleLeadership,
+ afterDelaySec,
+ TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Purges locks held by inactive nodes and evicts inactive nodes from candidacy.
+ */
+ private void purgeStaleLeadership() {
+ AtomicBoolean rerunPurge = new AtomicBoolean(false);
+ try {
+ staleLeadershipPurgeScheduled.set(false);
+ leaderMap.entrySet()
+ .stream()
+ .filter(e -> clusterService.getState(e.getValue().value()) == INACTIVE)
+ .forEach(entry -> {
+ String path = entry.getKey();
+ NodeId nodeId = entry.getValue().value();
+ try {
+ leaderMap.computeIf(path, nodeId::equals, (topic, leader) -> null);
+ } catch (Exception e) {
+ log.debug("Failed to purge stale lock held by {} for {}", nodeId, path, e);
+ rerunPurge.set(true);
+ }
+ });
+
+ candidateMap.entrySet()
+ .forEach(entry -> {
+ String path = entry.getKey();
+ Versioned<List<NodeId>> candidates = entry.getValue();
+ List<NodeId> candidatesList = candidates != null
+ ? candidates.value() : Collections.emptyList();
+ List<NodeId> activeCandidatesList =
+ candidatesList.stream()
+ .filter(n -> clusterService.getState(n) == ACTIVE)
+ .filter(n -> !localNodeId.equals(n) || activeTopics.contains(path))
+ .collect(Collectors.toList());
+ if (activeCandidatesList.size() < candidatesList.size()) {
+ Set<NodeId> removedCandidates =
+ Sets.difference(Sets.newHashSet(candidatesList),
+ Sets.newHashSet(activeCandidatesList));
+ try {
+ candidateMap.computeIf(path,
+ c -> c.stream()
+ .filter(n -> clusterService.getState(n) == INACTIVE)
+ .count() > 0,
+ (topic, c) -> c.stream()
+ .filter(n -> clusterService.getState(n) == ACTIVE)
+ .filter(n -> !localNodeId.equals(n) ||
+ activeTopics.contains(path))
+ .collect(Collectors.toList()));
+ } catch (Exception e) {
+ log.debug("Failed to evict inactive candidates {} from "
+ + "candidate list for {}", removedCandidates, path, e);
+ rerunPurge.set(true);
+ }
+ }
+ });
+ } catch (Exception e) {
+ log.debug("Failure purging state leadership.", e);
+ rerunPurge.set(true);
+ }
+
+ if (rerunPurge.get()) {
+ log.debug("Rescheduling stale leadership purge due to errors encountered in previous run");
+ scheduleStaleLeadershipPurge(DELAY_BETWEEN_STALE_LEADERSHIP_PURGE_ATTEMPTS_SEC);
+ }
+ }
+
+ private void refreshLeaderBoard() {
+ try {
+ Map<String, Leadership> newLeaderBoard = Maps.newHashMap();
+ leaderMap.entrySet().forEach(entry -> {
+ String path = entry.getKey();
+ Versioned<NodeId> leader = entry.getValue();
+ Leadership leadership = new Leadership(path,
+ leader.value(),
+ leader.version(),
+ leader.creationTime());
+ newLeaderBoard.put(path, leadership);
+ });
+
+ // first take snapshot of current leader board.
+ Map<String, Leadership> currentLeaderBoard = ImmutableMap.copyOf(leaderBoard);
+
+ MapDifference<String, Leadership> diff = Maps.difference(currentLeaderBoard, newLeaderBoard);
+
+ // evict stale leaders
+ diff.entriesOnlyOnLeft().forEach((path, leadership) -> {
+ log.debug("Evicting {} from leaderboard. It is no longer active leader.", leadership);
+ onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_BOOTED, leadership));
+ });
+
+ // add missing leaders
+ diff.entriesOnlyOnRight().forEach((path, leadership) -> {
+ log.debug("Adding {} to leaderboard. It is now the active leader.", leadership);
+ onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, leadership));
+ });
+
+ // add updated leaders
+ diff.entriesDiffering().forEach((path, difference) -> {
+ Leadership current = difference.leftValue();
+ Leadership updated = difference.rightValue();
+ if (current.epoch() < updated.epoch()) {
+ log.debug("Updated {} in leaderboard.", updated);
+ onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, updated));
+ }
+ });
+ } catch (Exception e) {
+ log.debug("Failed to refresh leader board", e);
+ }
+ }
+
+ private class InternalClusterEventListener implements ClusterEventListener {
+
+ @Override
+ public void event(ClusterEvent event) {
+ if (event.type() == Type.INSTANCE_DEACTIVATED || event.type() == Type.INSTANCE_REMOVED) {
+ scheduleStaleLeadershipPurge(0);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Match.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Match.java
new file mode 100644
index 00000000..5f707d62
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Match.java
@@ -0,0 +1,129 @@
+/*
+ * 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.store.consistent.impl;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Utility class for checking matching values.
+ *
+ * @param <T> type of value
+ */
+public final class Match<T> {
+
+ private final boolean matchAny;
+ private final T value;
+
+ /**
+ * Returns a Match that matches any value.
+ * @param <T> match type
+ * @return new instance
+ */
+ public static <T> Match<T> any() {
+ return new Match<>();
+ }
+
+ /**
+ * Returns a Match that matches null values.
+ * @param <T> match type
+ * @return new instance
+ */
+ public static <T> Match<T> ifNull() {
+ return ifValue(null);
+ }
+
+ /**
+ * Returns a Match that matches only specified value.
+ * @param value value to match
+ * @param <T> match type
+ * @return new instance
+ */
+ public static <T> Match<T> ifValue(T value) {
+ return new Match<>(value);
+ }
+
+ private Match() {
+ matchAny = true;
+ value = null;
+ }
+
+ private Match(T value) {
+ matchAny = false;
+ this.value = value;
+ }
+
+ /**
+ * Maps this instance to a Match of another type.
+ * @param mapper transformation function
+ * @param <V> new match type
+ * @return new instance
+ */
+ public <V> Match<V> map(Function<T, V> mapper) {
+ if (matchAny) {
+ return any();
+ } else if (value == null) {
+ return ifNull();
+ } else {
+ return ifValue(mapper.apply(value));
+ }
+ }
+
+ /**
+ * Checks if this instance matches specified value.
+ * @param other other value
+ * @return true if matches; false otherwise
+ */
+ public boolean matches(T other) {
+ if (matchAny) {
+ return true;
+ } else if (other == null) {
+ return value == null;
+ } else {
+ if (value instanceof byte[]) {
+ return Arrays.equals((byte[]) value, (byte[]) other);
+ }
+ return Objects.equals(value, other);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(matchAny, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Match)) {
+ return false;
+ }
+ Match<T> that = (Match<T>) other;
+ return Objects.equals(this.matchAny, that.matchAny) &&
+ Objects.equals(this.value, that.value);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("matchAny", matchAny)
+ .add("value", value)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/MeteringAgent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/MeteringAgent.java
new file mode 100644
index 00000000..6475bf7b
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/MeteringAgent.java
@@ -0,0 +1,134 @@
+/*
+ * 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.store.consistent.impl;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+import com.google.common.collect.Maps;
+import org.onlab.metrics.MetricsComponent;
+import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.MetricsService;
+import org.onlab.osgi.DefaultServiceDirectory;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Agent that implements usage and performance monitoring via the metrics service.
+ */
+public class MeteringAgent {
+
+ private Counter exceptionCounter;
+ private Counter perObjExceptionCounter;
+ private MetricsService metricsService;
+ private MetricsComponent metricsComponent;
+ private MetricsFeature metricsFeature;
+ private final Map<String, Timer> perObjOpTimers = Maps.newConcurrentMap();
+ private final Map<String, Timer> perOpTimers = Maps.newConcurrentMap();
+ private Timer perPrimitiveTimer;
+ private Timer perObjTimer;
+ private MetricsFeature wildcard;
+ private final boolean activated;
+ private Context nullTimer;
+
+ /**
+ * Constructs a new MeteringAgent for a given distributed primitive.
+ * Instantiates the metrics service
+ * Initializes all the general metrics for that object
+ *
+ * @param primitiveName Type of primitive to be metered
+ * @param objName Global name of the primitive
+ * @param activated boolean flag for whether metering is enabled or not
+ */
+ public MeteringAgent(String primitiveName, String objName, boolean activated) {
+ checkNotNull(objName, "Object name cannot be null");
+ this.activated = activated;
+ nullTimer = new Context(null, "");
+ if (this.activated) {
+ this.metricsService = DefaultServiceDirectory.getService(MetricsService.class);
+ this.metricsComponent = metricsService.registerComponent(primitiveName);
+ this.metricsFeature = metricsComponent.registerFeature(objName);
+ this.wildcard = metricsComponent.registerFeature("*");
+ this.perObjTimer = metricsService.createTimer(metricsComponent, metricsFeature, "*");
+ this.perPrimitiveTimer = metricsService.createTimer(metricsComponent, wildcard, "*");
+ this.perObjExceptionCounter = metricsService.createCounter(metricsComponent, metricsFeature, "exceptions");
+ this.exceptionCounter = metricsService.createCounter(metricsComponent, wildcard, "exceptions");
+ }
+ }
+
+ /**
+ * Initializes a specific timer for a given operation.
+ *
+ * @param op Specific operation being metered
+ * @return timer context
+ */
+ public Context startTimer(String op) {
+ if (!activated) {
+ return nullTimer;
+ }
+ // Check if timer exists, if it doesn't creates it
+ final Timer currTimer = perObjOpTimers.computeIfAbsent(op, timer ->
+ metricsService.createTimer(metricsComponent, metricsFeature, op));
+ perOpTimers.computeIfAbsent(op, timer -> metricsService.createTimer(metricsComponent, wildcard, op));
+ // Starts timer
+ return new Context(currTimer.time(), op);
+ }
+
+ /**
+ * Timer.Context with a specific operation.
+ */
+ public class Context {
+ private final Timer.Context context;
+ private final String operation;
+
+ /**
+ * Constructs Context.
+ *
+ * @param context context
+ * @param operation operation name
+ */
+ public Context(Timer.Context context, String operation) {
+ this.context = context;
+ this.operation = operation;
+ }
+
+ /**
+ * Stops timer given a specific context and updates all related metrics.
+ * @param e throwable
+ */
+ public void stop(Throwable e) {
+ if (!activated) {
+ return;
+ }
+ if (e == null) {
+ //Stop and updates timer with specific measurements per map, per operation
+ final long time = context.stop();
+ //updates timer with aggregated measurements per map
+ perOpTimers.get(operation).update(time, TimeUnit.NANOSECONDS);
+ //updates timer with aggregated measurements per map
+ perObjTimer.update(time, TimeUnit.NANOSECONDS);
+ //updates timer with aggregated measurements per all Consistent Maps
+ perPrimitiveTimer.update(time, TimeUnit.NANOSECONDS);
+ } else {
+ exceptionCounter.inc();
+ perObjExceptionCounter.inc();
+ }
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/PartitionedDatabase.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/PartitionedDatabase.java
new file mode 100644
index 00000000..a294681e
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/PartitionedDatabase.java
@@ -0,0 +1,386 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import net.kuujo.copycat.Task;
+import net.kuujo.copycat.cluster.Cluster;
+import net.kuujo.copycat.resource.ResourceState;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * A database that partitions the keys across one or more database partitions.
+ */
+public class PartitionedDatabase implements Database {
+
+ private final String name;
+ private final Partitioner<String> partitioner;
+ private final List<Database> partitions;
+ private final AtomicBoolean isOpen = new AtomicBoolean(false);
+ private static final String DB_NOT_OPEN = "Partitioned Database is not open";
+ private TransactionManager transactionManager;
+
+ public PartitionedDatabase(
+ String name,
+ Collection<Database> partitions) {
+ this.name = name;
+ this.partitions = partitions
+ .stream()
+ .sorted((db1, db2) -> db1.name().compareTo(db2.name()))
+ .collect(Collectors.toList());
+ this.partitioner = new SimpleKeyHashPartitioner(this.partitions);
+ }
+
+ /**
+ * Returns the databases for individual partitions.
+ * @return list of database partitions
+ */
+ public List<Database> getPartitions() {
+ return partitions;
+ }
+
+ /**
+ * Returns true if the database is open.
+ * @return true if open, false otherwise
+ */
+ @Override
+ public boolean isOpen() {
+ return isOpen.get();
+ }
+
+ @Override
+ public CompletableFuture<Set<String>> maps() {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ Set<String> mapNames = Sets.newConcurrentHashSet();
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(db -> db.maps().thenApply(mapNames::addAll))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> mapNames);
+ }
+
+ @Override
+ public CompletableFuture<Map<String, Long>> counters() {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ Map<String, Long> counters = Maps.newConcurrentMap();
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(db -> db.counters()
+ .thenApply(m -> {
+ counters.putAll(m);
+ return null;
+ }))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> counters);
+ }
+
+ @Override
+ public CompletableFuture<Integer> mapSize(String mapName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ AtomicInteger totalSize = new AtomicInteger(0);
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(p -> p.mapSize(mapName).thenApply(totalSize::addAndGet))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> totalSize.get());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> mapIsEmpty(String mapName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return mapSize(mapName).thenApply(size -> size == 0);
+ }
+
+ @Override
+ public CompletableFuture<Boolean> mapContainsKey(String mapName, String key) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(mapName, key).mapContainsKey(mapName, key);
+ }
+
+ @Override
+ public CompletableFuture<Boolean> mapContainsValue(String mapName, byte[] value) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ AtomicBoolean containsValue = new AtomicBoolean(false);
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(p -> p.mapContainsValue(mapName, value)
+ .thenApply(v -> containsValue.compareAndSet(false, v)))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> containsValue.get());
+ }
+
+ @Override
+ public CompletableFuture<Versioned<byte[]>> mapGet(String mapName, String key) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(mapName, key).mapGet(mapName, key);
+ }
+
+ @Override
+ public CompletableFuture<Result<UpdateResult<String, byte[]>>> mapUpdate(
+ String mapName, String key, Match<byte[]> valueMatch,
+ Match<Long> versionMatch, byte[] value) {
+ return partitioner.getPartition(mapName, key).mapUpdate(mapName, key, valueMatch, versionMatch, value);
+
+ }
+
+ @Override
+ public CompletableFuture<Result<Void>> mapClear(String mapName) {
+ AtomicBoolean isLocked = new AtomicBoolean(false);
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(p -> p.mapClear(mapName)
+ .thenApply(v -> isLocked.compareAndSet(false, Result.Status.LOCKED == v.status())))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> isLocked.get() ? Result.locked() : Result.ok(null));
+ }
+
+ @Override
+ public CompletableFuture<Set<String>> mapKeySet(String mapName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ Set<String> keySet = Sets.newConcurrentHashSet();
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(p -> p.mapKeySet(mapName).thenApply(keySet::addAll))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> keySet);
+ }
+
+ @Override
+ public CompletableFuture<Collection<Versioned<byte[]>>> mapValues(String mapName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ List<Versioned<byte[]>> values = new CopyOnWriteArrayList<>();
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(p -> p.mapValues(mapName).thenApply(values::addAll))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> values);
+ }
+
+ @Override
+ public CompletableFuture<Set<Entry<String, Versioned<byte[]>>>> mapEntrySet(String mapName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ Set<Entry<String, Versioned<byte[]>>> entrySet = Sets.newConcurrentHashSet();
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(p -> p.mapEntrySet(mapName).thenApply(entrySet::addAll))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> entrySet);
+ }
+
+ @Override
+ public CompletableFuture<Long> counterGet(String counterName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(counterName, counterName).counterGet(counterName);
+ }
+
+ @Override
+ public CompletableFuture<Long> counterAddAndGet(String counterName, long delta) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(counterName, counterName).counterAddAndGet(counterName, delta);
+ }
+
+ @Override
+ public CompletableFuture<Long> counterGetAndAdd(String counterName, long delta) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(counterName, counterName).counterGetAndAdd(counterName, delta);
+ }
+
+
+ @Override
+ public CompletableFuture<Long> queueSize(String queueName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(queueName, queueName).queueSize(queueName);
+ }
+
+ @Override
+ public CompletableFuture<Void> queuePush(String queueName, byte[] entry) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(queueName, queueName).queuePush(queueName, entry);
+ }
+
+ @Override
+ public CompletableFuture<byte[]> queuePop(String queueName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(queueName, queueName).queuePop(queueName);
+ }
+
+ @Override
+ public CompletableFuture<byte[]> queuePeek(String queueName) {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return partitioner.getPartition(queueName, queueName).queuePeek(queueName);
+ }
+
+ @Override
+ public CompletableFuture<CommitResponse> prepareAndCommit(Transaction transaction) {
+ Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
+ if (subTransactions.isEmpty()) {
+ return CompletableFuture.completedFuture(CommitResponse.success(ImmutableList.of()));
+ } else if (subTransactions.size() == 1) {
+ Entry<Database, Transaction> entry =
+ subTransactions.entrySet().iterator().next();
+ return entry.getKey().prepareAndCommit(entry.getValue());
+ } else {
+ if (transactionManager == null) {
+ throw new IllegalStateException("TransactionManager is not initialized");
+ }
+ return transactionManager.execute(transaction);
+ }
+ }
+
+ @Override
+ public CompletableFuture<Boolean> prepare(Transaction transaction) {
+ Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
+ AtomicBoolean status = new AtomicBoolean(true);
+ return CompletableFuture.allOf(subTransactions.entrySet()
+ .stream()
+ .map(entry -> entry
+ .getKey()
+ .prepare(entry.getValue())
+ .thenApply(v -> status.compareAndSet(true, v)))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> status.get());
+ }
+
+ @Override
+ public CompletableFuture<CommitResponse> commit(Transaction transaction) {
+ Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
+ AtomicBoolean success = new AtomicBoolean(true);
+ List<UpdateResult<String, byte[]>> allUpdates = Lists.newArrayList();
+ return CompletableFuture.allOf(subTransactions.entrySet()
+ .stream()
+ .map(entry -> entry.getKey().commit(entry.getValue())
+ .thenAccept(response -> {
+ success.set(success.get() && response.success());
+ if (success.get()) {
+ allUpdates.addAll(response.updates());
+ }
+ }))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> success.get() ?
+ CommitResponse.success(allUpdates) : CommitResponse.failure());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> rollback(Transaction transaction) {
+ Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
+ return CompletableFuture.allOf(subTransactions.entrySet()
+ .stream()
+ .map(entry -> entry.getKey().rollback(entry.getValue()))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> true);
+ }
+
+ @Override
+ public CompletableFuture<Database> open() {
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(Database::open)
+ .toArray(CompletableFuture[]::new))
+ .thenApply(v -> {
+ isOpen.set(true);
+ return this;
+ });
+ }
+
+ @Override
+ public CompletableFuture<Void> close() {
+ checkState(isOpen.get(), DB_NOT_OPEN);
+ return CompletableFuture.allOf(partitions
+ .stream()
+ .map(database -> database.close())
+ .toArray(CompletableFuture[]::new));
+ }
+
+ @Override
+ public boolean isClosed() {
+ return !isOpen.get();
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Cluster cluster() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Database addStartupTask(Task<CompletableFuture<Void>> task) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Database addShutdownTask(Task<CompletableFuture<Void>> task) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResourceState state() {
+ throw new UnsupportedOperationException();
+ }
+
+ private Map<Database, Transaction> createSubTransactions(
+ Transaction transaction) {
+ Map<Database, List<DatabaseUpdate>> perPartitionUpdates = Maps.newHashMap();
+ for (DatabaseUpdate update : transaction.updates()) {
+ Database partition = partitioner.getPartition(update.mapName(), update.key());
+ List<DatabaseUpdate> partitionUpdates =
+ perPartitionUpdates.computeIfAbsent(partition, k -> Lists.newLinkedList());
+ partitionUpdates.add(update);
+ }
+ Map<Database, Transaction> subTransactions = Maps.newHashMap();
+ perPartitionUpdates.forEach((k, v) -> subTransactions.put(k, new DefaultTransaction(transaction.id(), v)));
+ return subTransactions;
+ }
+
+ protected void setTransactionManager(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ @Override
+ public void registerConsumer(Consumer<StateMachineUpdate> consumer) {
+ partitions.forEach(p -> p.registerConsumer(consumer));
+ }
+
+ @Override
+ public void unregisterConsumer(Consumer<StateMachineUpdate> consumer) {
+ partitions.forEach(p -> p.unregisterConsumer(consumer));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Partitioner.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Partitioner.java
new file mode 100644
index 00000000..de630b90
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Partitioner.java
@@ -0,0 +1,33 @@
+/*
+ * 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.store.consistent.impl;
+
+/**
+ * Partitioner is responsible for mapping keys to individual database partitions.
+ *
+ * @param <K> key type.
+ */
+public interface Partitioner<K> {
+
+ /**
+ * Returns the database partition.
+ * @param mapName map name
+ * @param key key
+ * @return Database partition
+ */
+ Database getPartition(String mapName, K key);
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Result.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Result.java
new file mode 100644
index 00000000..856f706d
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/Result.java
@@ -0,0 +1,121 @@
+/*
+ * 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.store.consistent.impl;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Objects;
+
+/**
+ * Result of a database update operation.
+ *
+ * @param <V> return value type
+ */
+public final class Result<V> {
+
+ public enum Status {
+ /**
+ * Indicates a successful update.
+ */
+ OK,
+
+ /**
+ * Indicates a failure due to underlying state being locked by another transaction.
+ */
+ LOCKED
+ }
+
+ private final Status status;
+ private final V value;
+
+ /**
+ * Creates a new Result instance with the specified value with status set to Status.OK.
+ *
+ * @param <V> result value type
+ * @param value result value
+ * @return Result instance
+ */
+ public static <V> Result<V> ok(V value) {
+ return new Result<>(value, Status.OK);
+ }
+
+ /**
+ * Creates a new Result instance with status set to Status.LOCKED.
+ *
+ * @param <V> result value type
+ * @return Result instance
+ */
+ public static <V> Result<V> locked() {
+ return new Result<>(null, Status.LOCKED);
+ }
+
+ private Result(V value, Status status) {
+ this.value = value;
+ this.status = status;
+ }
+
+ /**
+ * Returns true if this result indicates a successful execution i.e status is Status.OK.
+ *
+ * @return true if successful, false otherwise
+ */
+ public boolean success() {
+ return status == Status.OK;
+ }
+
+ /**
+ * Returns the status of database update operation.
+ *
+ * @return database update status
+ */
+ public Status status() {
+ return status;
+ }
+
+ /**
+ * Returns the return value for the update.
+ *
+ * @return value returned by database update. If the status is another
+ * other than Status.OK, this returns a null
+ */
+ public V value() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, status);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Result)) {
+ return false;
+ }
+ Result<V> that = (Result<V>) other;
+ return Objects.equals(this.value, that.value) &&
+ Objects.equals(this.status, that.status);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("status", status)
+ .add("value", value)
+ .toString();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleKeyHashPartitioner.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleKeyHashPartitioner.java
new file mode 100644
index 00000000..40864286
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleKeyHashPartitioner.java
@@ -0,0 +1,38 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.List;
+
+/**
+ * A simple Partitioner for mapping keys to database partitions.
+ * <p>
+ * This class uses a md5 hash based hashing scheme for hashing the key to
+ * a partition.
+ *
+ */
+public class SimpleKeyHashPartitioner extends DatabasePartitioner {
+
+ public SimpleKeyHashPartitioner(List<Database> partitions) {
+ super(partitions);
+ }
+
+ @Override
+ public Database getPartition(String mapName, String key) {
+ return partitions.get(hash(key) % partitions.size());
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleTableHashPartitioner.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleTableHashPartitioner.java
new file mode 100644
index 00000000..8dc26e0f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/SimpleTableHashPartitioner.java
@@ -0,0 +1,39 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.List;
+
+/**
+ * A simple Partitioner that uses the map name hash to
+ * pick a partition.
+ * <p>
+ * This class uses a md5 hash based hashing scheme for hashing the map name to
+ * a partition. This partitioner maps all keys for a map to the same database
+ * partition.
+ */
+public class SimpleTableHashPartitioner extends DatabasePartitioner {
+
+ public SimpleTableHashPartitioner(List<Database> partitions) {
+ super(partitions);
+ }
+
+ @Override
+ public Database getPartition(String mapName, String key) {
+ return partitions.get(hash(mapName) % partitions.size());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/StateMachineUpdate.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/StateMachineUpdate.java
new file mode 100644
index 00000000..72356d0b
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/StateMachineUpdate.java
@@ -0,0 +1,91 @@
+/*
+ * 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.store.consistent.impl;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Representation of a state machine update.
+ */
+public class StateMachineUpdate {
+
+ /**
+ * Target data structure type this update is for.
+ */
+ enum Target {
+ /**
+ * Update is for a map.
+ */
+ MAP_UPDATE,
+
+ /**
+ * Update is a transaction commit.
+ */
+ TX_COMMIT,
+
+ /**
+ * Update is a queue push.
+ */
+ QUEUE_PUSH,
+
+ /**
+ * Update is for some other operation.
+ */
+ OTHER
+ }
+
+ private final String operationName;
+ private final Object input;
+ private final Object output;
+
+ public StateMachineUpdate(String operationName, Object input, Object output) {
+ this.operationName = operationName;
+ this.input = input;
+ this.output = output;
+ }
+
+ public Target target() {
+ // FIXME: This check is brittle
+ if (operationName.contains("mapUpdate")) {
+ return Target.MAP_UPDATE;
+ } else if (operationName.contains("commit") || operationName.contains("prepareAndCommit")) {
+ return Target.TX_COMMIT;
+ } else if (operationName.contains("queuePush")) {
+ return Target.QUEUE_PUSH;
+ } else {
+ return Target.OTHER;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T input() {
+ return (T) input;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T output() {
+ return (T) output;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("name", operationName)
+ .add("input", input)
+ .add("output", output)
+ .toString();
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/TransactionManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/TransactionManager.java
new file mode 100644
index 00000000..fc6e58d0
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/TransactionManager.java
@@ -0,0 +1,126 @@
+/*
+ * 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.store.consistent.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.AsyncConsistentMap;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.DatabaseUpdate;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.Transaction;
+import org.onosproject.store.service.Versioned;
+import org.onosproject.store.service.Transaction.State;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Agent that runs the two phase commit protocol.
+ */
+public class TransactionManager {
+
+ private static final KryoNamespace KRYO_NAMESPACE = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.BASIC)
+ .nextId(KryoNamespace.FLOATING_ID)
+ .register(Versioned.class)
+ .register(DatabaseUpdate.class)
+ .register(DatabaseUpdate.Type.class)
+ .register(DefaultTransaction.class)
+ .register(Transaction.State.class)
+ .build();
+
+ private final Serializer serializer = Serializer.using(Arrays.asList(KRYO_NAMESPACE));
+ private final Database database;
+ private final AsyncConsistentMap<Long, Transaction> transactions;
+
+ /**
+ * Constructs a new TransactionManager for the specified database instance.
+ *
+ * @param database database
+ * @param mapBuilder builder for ConsistentMap instances
+ */
+ public TransactionManager(Database database, ConsistentMapBuilder<Long, Transaction> mapBuilder) {
+ this.database = checkNotNull(database, "database cannot be null");
+ this.transactions = mapBuilder.withName("onos-transactions")
+ .withSerializer(serializer)
+ .buildAsyncMap();
+ }
+
+ /**
+ * Executes the specified transaction by employing a two phase commit protocol.
+ *
+ * @param transaction transaction to commit
+ * @return transaction result. Result value true indicates a successful commit, false
+ * indicates abort
+ */
+ public CompletableFuture<CommitResponse> execute(Transaction transaction) {
+ // clean up if this transaction in already in a terminal state.
+ if (transaction.state() == Transaction.State.COMMITTED ||
+ transaction.state() == Transaction.State.ROLLEDBACK) {
+ return transactions.remove(transaction.id()).thenApply(v -> CommitResponse.success(ImmutableList.of()));
+ } else if (transaction.state() == Transaction.State.COMMITTING) {
+ return commit(transaction);
+ } else if (transaction.state() == Transaction.State.ROLLINGBACK) {
+ return rollback(transaction).thenApply(v -> CommitResponse.success(ImmutableList.of()));
+ } else {
+ return prepare(transaction).thenCompose(v -> v ? commit(transaction) : rollback(transaction));
+ }
+ }
+
+
+ /**
+ * Returns all transactions in the system.
+ *
+ * @return future for a collection of transactions
+ */
+ public CompletableFuture<Collection<Transaction>> getTransactions() {
+ return transactions.values().thenApply(c -> {
+ Collection<Transaction> txns = c.stream().map(v -> v.value()).collect(Collectors.toList());
+ return txns;
+ });
+ }
+
+ private CompletableFuture<Boolean> prepare(Transaction transaction) {
+ return transactions.put(transaction.id(), transaction)
+ .thenCompose(v -> database.prepare(transaction))
+ .thenCompose(status -> transactions.put(
+ transaction.id(),
+ transaction.transition(status ? State.COMMITTING : State.ROLLINGBACK))
+ .thenApply(v -> status));
+ }
+
+ private CompletableFuture<CommitResponse> commit(Transaction transaction) {
+ return database.commit(transaction)
+ .whenComplete((r, e) -> transactions.put(
+ transaction.id(),
+ transaction.transition(Transaction.State.COMMITTED)));
+ }
+
+ private CompletableFuture<CommitResponse> rollback(Transaction transaction) {
+ return database.rollback(transaction)
+ .thenCompose(v -> transactions.put(
+ transaction.id(),
+ transaction.transition(Transaction.State.ROLLEDBACK)))
+ .thenApply(v -> CommitResponse.failure());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/UpdateResult.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/UpdateResult.java
new file mode 100644
index 00000000..50b78dd4
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/UpdateResult.java
@@ -0,0 +1,85 @@
+/*
+ * 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.store.consistent.impl;
+
+import java.util.function.Function;
+
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.Versioned;
+
+/**
+ * Result of a update operation.
+ * <p>
+ * Both old and new values are accessible along with a flag that indicates if the
+ * the value was updated. If flag is false, oldValue and newValue both
+ * point to the same unmodified value.
+ * @param <V> result type
+ */
+public class UpdateResult<K, V> {
+
+ private final boolean updated;
+ private final String mapName;
+ private final K key;
+ private final Versioned<V> oldValue;
+ private final Versioned<V> newValue;
+
+ public UpdateResult(boolean updated, String mapName, K key, Versioned<V> oldValue, Versioned<V> newValue) {
+ this.updated = updated;
+ this.mapName = mapName;
+ this.key = key;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+
+ public boolean updated() {
+ return updated;
+ }
+
+ public String mapName() {
+ return mapName;
+ }
+
+ public K key() {
+ return key;
+ }
+
+ public Versioned<V> oldValue() {
+ return oldValue;
+ }
+
+ public Versioned<V> newValue() {
+ return newValue;
+ }
+
+ public <K1, V1> UpdateResult<K1, V1> map(Function<K, K1> keyTransform, Function<V, V1> valueMapper) {
+ return new UpdateResult<>(updated,
+ mapName,
+ keyTransform.apply(key),
+ oldValue == null ? null : oldValue.map(valueMapper),
+ newValue == null ? null : newValue.map(valueMapper));
+ }
+
+ public MapEvent<K, V> toMapEvent() {
+ if (!updated) {
+ return null;
+ } else {
+ MapEvent.Type eventType = oldValue == null ?
+ MapEvent.Type.INSERT : newValue == null ? MapEvent.Type.REMOVE : MapEvent.Type.UPDATE;
+ Versioned<V> eventValue = eventType == MapEvent.Type.REMOVE ? oldValue : newValue;
+ return new MapEvent<>(mapName(), eventType, key(), eventValue);
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/package-info.java
new file mode 100644
index 00000000..3dae86b5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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 partitioned and distributed store facility capable of
+ * providing consistent update semantics.
+ */
+package org.onosproject.store.consistent.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdEvent.java
new file mode 100644
index 00000000..9f021b13
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.store.core.impl;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Application ID event.
+ */
+public class AppIdEvent extends AbstractEvent<AppIdEvent.Type, ApplicationId> {
+
+ public enum Type {
+ APP_REGISTERED
+ }
+
+ protected AppIdEvent(Type type, ApplicationId subject) {
+ super(type, subject);
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdStoreDelegate.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdStoreDelegate.java
new file mode 100644
index 00000000..6240a311
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/AppIdStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.store.core.impl;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Application ID store delegate.
+ */
+public interface AppIdStoreDelegate extends StoreDelegate<AppIdEvent> {
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentApplicationIdStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentApplicationIdStore.java
new file mode 100644
index 00000000..e54b0ee5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentApplicationIdStore.java
@@ -0,0 +1,154 @@
+/*
+ * 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.store.core.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+
+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.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageException;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+/**
+ * ApplicationIdStore implementation on top of {@code AtomicCounter}
+ * and {@code ConsistentMap} primitives.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ConsistentApplicationIdStore implements ApplicationIdStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private AtomicCounter appIdCounter;
+ private ConsistentMap<String, ApplicationId> registeredIds;
+ private Map<String, ApplicationId> nameToAppIdCache = Maps.newConcurrentMap();
+ private Map<Short, ApplicationId> idToAppIdCache = Maps.newConcurrentMap();
+ private ScheduledExecutorService executor;
+
+ private static final Serializer SERIALIZER = Serializer.using(new KryoNamespace.Builder()
+ .register(KryoNamespaces.API)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .build());
+
+ @Activate
+ public void activate() {
+ appIdCounter = storageService.atomicCounterBuilder()
+ .withName("onos-app-id-counter")
+ .withPartitionsDisabled()
+ .build();
+
+ registeredIds = storageService.<String, ApplicationId>consistentMapBuilder()
+ .withName("onos-app-ids")
+ .withPartitionsDisabled()
+ .withSerializer(SERIALIZER)
+ .build();
+
+ primeAppIds();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ executor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public Set<ApplicationId> getAppIds() {
+ // TODO: Rework this when we have notification support in ConsistentMap.
+ primeAppIds();
+ return ImmutableSet.copyOf(nameToAppIdCache.values());
+ }
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ if (!idToAppIdCache.containsKey(id)) {
+ primeAppIds();
+ }
+ return idToAppIdCache.get(id);
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ ApplicationId appId = nameToAppIdCache.computeIfAbsent(name, key -> {
+ Versioned<ApplicationId> existingAppId = registeredIds.get(key);
+ return existingAppId != null ? existingAppId.value() : null;
+ });
+ if (appId != null) {
+ idToAppIdCache.putIfAbsent(appId.id(), appId);
+ }
+ return appId;
+ }
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ ApplicationId appId = nameToAppIdCache.computeIfAbsent(name, key -> {
+ Versioned<ApplicationId> existingAppId = registeredIds.get(name);
+ if (existingAppId == null) {
+ int id = Tools.retryable(appIdCounter::incrementAndGet, StorageException.class, 1, 2000)
+ .get()
+ .intValue();
+ DefaultApplicationId newAppId = new DefaultApplicationId(id, name);
+ existingAppId = registeredIds.putIfAbsent(name, newAppId);
+ if (existingAppId != null) {
+ return existingAppId.value();
+ } else {
+ return newAppId;
+ }
+ } else {
+ return existingAppId.value();
+ }
+ });
+ idToAppIdCache.putIfAbsent(appId.id(), appId);
+ return appId;
+ }
+
+ private void primeAppIds() {
+ registeredIds.values()
+ .stream()
+ .map(Versioned::value)
+ .forEach(appId -> {
+ nameToAppIdCache.putIfAbsent(appId.name(), appId);
+ idToAppIdCache.putIfAbsent(appId.id(), appId);
+ });
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentIdBlockStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentIdBlockStore.java
new file mode 100644
index 00000000..8913742d
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/ConsistentIdBlockStore.java
@@ -0,0 +1,64 @@
+package org.onosproject.store.core.impl;
+
+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.Tools;
+import org.onosproject.core.IdBlock;
+import org.onosproject.core.IdBlockStore;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageException;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of {@code IdBlockStore} using {@code AtomicCounter}.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ConsistentIdBlockStore implements IdBlockStore {
+
+ private static final int MAX_TRIES = 5;
+ private static final int RETRY_DELAY_MS = 2_000;
+
+ private final Logger log = getLogger(getClass());
+ private final Map<String, AtomicCounter> topicCounters = Maps.newConcurrentMap();
+
+ private static final long DEFAULT_BLOCK_SIZE = 0x100000L;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public IdBlock getIdBlock(String topic) {
+ AtomicCounter counter = topicCounters
+ .computeIfAbsent(topic,
+ name -> storageService.atomicCounterBuilder()
+ .withName(name)
+ .build());
+ Long blockBase = Tools.retryable(counter::getAndAdd,
+ StorageException.class,
+ MAX_TRIES,
+ RETRY_DELAY_MS).apply(DEFAULT_BLOCK_SIZE);
+ return new IdBlock(blockBase, DEFAULT_BLOCK_SIZE);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/LogicalClockManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/LogicalClockManager.java
new file mode 100644
index 00000000..ccf0f326
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/LogicalClockManager.java
@@ -0,0 +1,51 @@
+package org.onosproject.store.core.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+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.store.Timestamp;
+import org.onosproject.store.impl.LogicalTimestamp;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.LogicalClockService;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+/**
+ * LogicalClockService implementation based on a AtomicCounter.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class LogicalClockManager implements LogicalClockService {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private static final String SYSTEM_LOGICAL_CLOCK_COUNTER_NAME = "sys-clock-counter";
+ private AtomicCounter atomicCounter;
+
+ @Activate
+ public void activate() {
+ atomicCounter = storageService.atomicCounterBuilder()
+ .withName(SYSTEM_LOGICAL_CLOCK_COUNTER_NAME)
+ .withPartitionsDisabled()
+ .build();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public Timestamp getTimestamp() {
+ return new LogicalTimestamp(atomicCounter.incrementAndGet());
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/core/impl/package-info.java
new file mode 100644
index 00000000..bb758b10
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of a distributed application ID registry store using Hazelcast.
+ */
+package org.onosproject.store.core.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyAdvertisement.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyAdvertisement.java
new file mode 100644
index 00000000..491d1334
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyAdvertisement.java
@@ -0,0 +1,72 @@
+/*
+ * 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.store.device.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Timestamp;
+
+
+/**
+ * Device Advertisement message.
+ */
+public class DeviceAntiEntropyAdvertisement {
+
+ private final NodeId sender;
+ private final Map<DeviceFragmentId, Timestamp> deviceFingerPrints;
+ private final Map<PortFragmentId, Timestamp> portFingerPrints;
+ private final Map<DeviceId, Timestamp> offline;
+
+
+ public DeviceAntiEntropyAdvertisement(NodeId sender,
+ Map<DeviceFragmentId, Timestamp> devices,
+ Map<PortFragmentId, Timestamp> ports,
+ Map<DeviceId, Timestamp> offline) {
+ this.sender = checkNotNull(sender);
+ this.deviceFingerPrints = checkNotNull(devices);
+ this.portFingerPrints = checkNotNull(ports);
+ this.offline = checkNotNull(offline);
+ }
+
+ public NodeId sender() {
+ return sender;
+ }
+
+ public Map<DeviceFragmentId, Timestamp> deviceFingerPrints() {
+ return deviceFingerPrints;
+ }
+
+ public Map<PortFragmentId, Timestamp> ports() {
+ return portFingerPrints;
+ }
+
+ public Map<DeviceId, Timestamp> offline() {
+ return offline;
+ }
+
+ // For serializer
+ @SuppressWarnings("unused")
+ private DeviceAntiEntropyAdvertisement() {
+ this.sender = null;
+ this.deviceFingerPrints = null;
+ this.portFingerPrints = null;
+ this.offline = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyRequest.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyRequest.java
new file mode 100644
index 00000000..a719a770
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceAntiEntropyRequest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.store.device.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Message to request for other peers information.
+ */
+public class DeviceAntiEntropyRequest {
+
+ private final NodeId sender;
+ private final Collection<DeviceFragmentId> devices;
+ private final Collection<PortFragmentId> ports;
+
+ public DeviceAntiEntropyRequest(NodeId sender,
+ Collection<DeviceFragmentId> devices,
+ Collection<PortFragmentId> ports) {
+
+ this.sender = checkNotNull(sender);
+ this.devices = checkNotNull(devices);
+ this.ports = checkNotNull(ports);
+ }
+
+ public NodeId sender() {
+ return sender;
+ }
+
+ public Collection<DeviceFragmentId> devices() {
+ return devices;
+ }
+
+ public Collection<PortFragmentId> ports() {
+ return ports;
+ }
+
+ // For serializer
+ @SuppressWarnings("unused")
+ private DeviceAntiEntropyRequest() {
+ this.sender = null;
+ this.devices = null;
+ this.ports = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceClockManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceClockManager.java
new file mode 100644
index 00000000..da5bd5de
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceClockManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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.store.device.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+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.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+import org.slf4j.Logger;
+
+/**
+ * Clock service to issue Timestamp based on Device Mastership.
+ */
+@Component(immediate = true)
+@Service
+public class DeviceClockManager implements DeviceClockService {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipTermService mastershipTermService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ protected NodeId localNodeId;
+
+ private final AtomicLong ticker = new AtomicLong(0);
+
+ @Activate
+ public void activate() {
+ localNodeId = clusterService.getLocalNode().id();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public Timestamp getTimestamp(DeviceId deviceId) {
+ MastershipTerm term = mastershipTermService.getMastershipTerm(deviceId);
+ if (term == null || !localNodeId.equals(term.master())) {
+ throw new IllegalStateException("Requesting timestamp for " + deviceId + " without mastership");
+ }
+ return new MastershipBasedTimestamp(term.termNumber(), ticker.incrementAndGet());
+ }
+
+ @Override
+ public boolean isTimestampAvailable(DeviceId deviceId) {
+ MastershipTerm term = mastershipTermService.getMastershipTerm(deviceId);
+ return term != null && localNodeId.equals(term.master());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceDescriptions.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceDescriptions.java
new file mode 100644
index 00000000..fd7fcd80
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceDescriptions.java
@@ -0,0 +1,134 @@
+/*
+ * 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.store.device.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.DefaultAnnotations.union;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceDescription;
+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.onosproject.store.Timestamp;
+import org.onosproject.store.impl.Timestamped;
+
+/*
+ * Collection of Description of a Device and Ports, given from a Provider.
+ */
+class DeviceDescriptions {
+
+ private volatile Timestamped<DeviceDescription> deviceDesc;
+
+ private final ConcurrentMap<PortNumber, Timestamped<PortDescription>> portDescs;
+
+ public DeviceDescriptions(Timestamped<DeviceDescription> desc) {
+ this.deviceDesc = checkNotNull(desc);
+ this.portDescs = new ConcurrentHashMap<>();
+ }
+
+ public Timestamp getLatestTimestamp() {
+ Timestamp latest = deviceDesc.timestamp();
+ for (Timestamped<PortDescription> desc : portDescs.values()) {
+ if (desc.timestamp().compareTo(latest) > 0) {
+ latest = desc.timestamp();
+ }
+ }
+ return latest;
+ }
+
+ public Timestamped<DeviceDescription> getDeviceDesc() {
+ return deviceDesc;
+ }
+
+ public Timestamped<PortDescription> getPortDesc(PortNumber number) {
+ return portDescs.get(number);
+ }
+
+ public Map<PortNumber, Timestamped<PortDescription>> getPortDescs() {
+ return Collections.unmodifiableMap(portDescs);
+ }
+
+ /**
+ * Puts DeviceDescription, merging annotations as necessary.
+ *
+ * @param newDesc new DeviceDescription
+ */
+ public void putDeviceDesc(Timestamped<DeviceDescription> newDesc) {
+ Timestamped<DeviceDescription> oldOne = deviceDesc;
+ Timestamped<DeviceDescription> newOne = newDesc;
+ if (oldOne != null) {
+ SparseAnnotations merged = union(oldOne.value().annotations(),
+ newDesc.value().annotations());
+ newOne = new Timestamped<DeviceDescription>(
+ new DefaultDeviceDescription(newDesc.value(), merged),
+ newDesc.timestamp());
+ }
+ deviceDesc = newOne;
+ }
+
+ /**
+ * Puts PortDescription, merging annotations as necessary.
+ *
+ * @param newDesc new PortDescription
+ */
+ public void putPortDesc(Timestamped<PortDescription> newDesc) {
+ Timestamped<PortDescription> oldOne = portDescs.get(newDesc.value().portNumber());
+ Timestamped<PortDescription> newOne = newDesc;
+ if (oldOne != null) {
+ SparseAnnotations merged = union(oldOne.value().annotations(),
+ newDesc.value().annotations());
+ newOne = null;
+ switch (newDesc.value().type()) {
+ case OMS:
+ OmsPortDescription omsDesc = (OmsPortDescription) (newDesc.value());
+ newOne = new Timestamped<PortDescription>(
+ new OmsPortDescription(
+ omsDesc, omsDesc.minFrequency(), omsDesc.maxFrequency(), omsDesc.grid(), merged),
+ newDesc.timestamp());
+ break;
+ case OCH:
+ OchPortDescription ochDesc = (OchPortDescription) (newDesc.value());
+ newOne = new Timestamped<PortDescription>(
+ new OchPortDescription(
+ ochDesc, ochDesc.signalType(), ochDesc.isTunable(), ochDesc.lambda(), merged),
+ newDesc.timestamp());
+ break;
+ case ODUCLT:
+ OduCltPortDescription ocDesc = (OduCltPortDescription) (newDesc.value());
+ newOne = new Timestamped<PortDescription>(
+ new OduCltPortDescription(
+ ocDesc, ocDesc.signalType(), merged),
+ newDesc.timestamp());
+ break;
+ default:
+ newOne = new Timestamped<PortDescription>(
+ new DefaultPortDescription(newDesc.value(), merged),
+ newDesc.timestamp());
+ }
+ }
+ portDescs.put(newOne.value().portNumber(), newOne);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceFragmentId.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceFragmentId.java
new file mode 100644
index 00000000..214f4c23
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceFragmentId.java
@@ -0,0 +1,69 @@
+/*
+ * 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.store.device.impl;
+
+import java.util.Objects;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Identifier for DeviceDesctiption from a Provider.
+ */
+public final class DeviceFragmentId {
+ public final ProviderId providerId;
+ public final DeviceId deviceId;
+
+ public DeviceFragmentId(DeviceId deviceId, ProviderId providerId) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerId, deviceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DeviceFragmentId)) {
+ return false;
+ }
+ DeviceFragmentId that = (DeviceFragmentId) obj;
+ return Objects.equals(this.deviceId, that.deviceId) &&
+ Objects.equals(this.providerId, that.providerId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private DeviceFragmentId() {
+ this.providerId = null;
+ this.deviceId = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceInjectedEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceInjectedEvent.java
new file mode 100644
index 00000000..6f93963a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceInjectedEvent.java
@@ -0,0 +1,49 @@
+package org.onosproject.store.device.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.provider.ProviderId;
+
+public class DeviceInjectedEvent {
+ private final ProviderId providerId;
+ private final DeviceId deviceId;
+ private final DeviceDescription deviceDescription;
+
+ protected DeviceInjectedEvent(
+ ProviderId providerId,
+ DeviceId deviceId,
+ DeviceDescription deviceDescription) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.deviceDescription = deviceDescription;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public DeviceDescription deviceDescription() {
+ return deviceDescription;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("deviceDescription", deviceDescription)
+ .toString();
+ }
+
+ // for serializer
+ protected DeviceInjectedEvent() {
+ this.providerId = null;
+ this.deviceId = null;
+ this.deviceDescription = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceKey.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceKey.java
new file mode 100644
index 00000000..0896bf18
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/DeviceKey.java
@@ -0,0 +1,70 @@
+/*
+ * 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.store.device.impl;
+
+import java.util.Objects;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Key for DeviceDescriptions in ECDeviceStore.
+ */
+public class DeviceKey {
+ private final ProviderId providerId;
+ private final DeviceId deviceId;
+
+ public DeviceKey(ProviderId providerId, DeviceId deviceId) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerId, deviceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DeviceKey)) {
+ return false;
+ }
+ DeviceKey that = (DeviceKey) obj;
+ return Objects.equals(this.deviceId, that.deviceId) &&
+ Objects.equals(this.providerId, that.providerId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/ECDeviceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/ECDeviceStore.java
new file mode 100644
index 00000000..2dae55bb
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/ECDeviceStore.java
@@ -0,0 +1,784 @@
+/*
+ * 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.store.device.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+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.ChassisId;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.SharedExecutors;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.AnnotationsUtil;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+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.Device.Type;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceStore;
+import org.onosproject.net.device.DeviceStoreDelegate;
+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.onosproject.net.device.PortStatistics;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.serializers.custom.DistributedStoreSerializers;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.SetEvent;
+import org.onosproject.store.service.SetEventListener;
+import org.onosproject.store.service.WallClockTimestamp;
+
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.onosproject.store.device.impl.GossipDeviceStoreMessageSubjects.DEVICE_INJECTED;
+import static org.onosproject.store.device.impl.GossipDeviceStoreMessageSubjects.DEVICE_REMOVE_REQ;
+import static org.onosproject.store.device.impl.GossipDeviceStoreMessageSubjects.PORT_INJECTED;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+
+/**
+ * Manages the inventory of devices using a {@code EventuallyConsistentMap}.
+ */
+@Component(immediate = true, enabled = false)
+@Service
+public class ECDeviceStore
+ extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
+ implements DeviceStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
+
+ private final Map<DeviceId, Device> devices = Maps.newConcurrentMap();
+ private final Map<DeviceId, Map<PortNumber, Port>> devicePorts = Maps.newConcurrentMap();
+ Set<DeviceId> pendingAvailableChangeUpdates = Sets.newConcurrentHashSet();
+
+ private EventuallyConsistentMap<DeviceKey, DeviceDescription> deviceDescriptions;
+ private EventuallyConsistentMap<PortKey, PortDescription> portDescriptions;
+ private EventuallyConsistentMap<DeviceId, Map<PortNumber, PortStatistics>> devicePortStats;
+ private EventuallyConsistentMap<DeviceId, Map<PortNumber, PortStatistics>> devicePortDeltaStats;
+
+ private DistributedSet<DeviceId> availableDevices;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipTermService mastershipTermService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceClockService deviceClockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ private NodeId localNodeId;
+ private EventuallyConsistentMapListener<DeviceKey, DeviceDescription> deviceUpdateListener =
+ new InternalDeviceChangeEventListener();
+ private EventuallyConsistentMapListener<PortKey, PortDescription> portUpdateListener =
+ new InternalPortChangeEventListener();
+ private final EventuallyConsistentMapListener<DeviceId, Map<PortNumber, PortStatistics>> portStatsListener =
+ new InternalPortStatsListener();
+ private final SetEventListener<DeviceId> deviceStatusTracker =
+ new InternalDeviceStatusTracker();
+
+ protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(DistributedStoreSerializers.STORE_COMMON)
+ .nextId(DistributedStoreSerializers.STORE_CUSTOM_BEGIN)
+ .register(DeviceInjectedEvent.class)
+ .register(PortInjectedEvent.class)
+ .build();
+ }
+ };
+
+ protected static final KryoNamespace.Builder SERIALIZER_BUILDER = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(DeviceKey.class)
+ .register(PortKey.class)
+ .register(DeviceKey.class)
+ .register(PortKey.class)
+ .register(MastershipBasedTimestamp.class);
+
+ @Activate
+ public void activate() {
+ localNodeId = clusterService.getLocalNode().id();
+
+ deviceDescriptions = storageService.<DeviceKey, DeviceDescription>eventuallyConsistentMapBuilder()
+ .withName("onos-device-descriptions")
+ .withSerializer(SERIALIZER_BUILDER)
+ .withTimestampProvider((k, v) -> {
+ try {
+ return deviceClockService.getTimestamp(k.deviceId());
+ } catch (IllegalStateException e) {
+ return null;
+ }
+ }).build();
+
+ portDescriptions = storageService.<PortKey, PortDescription>eventuallyConsistentMapBuilder()
+ .withName("onos-port-descriptions")
+ .withSerializer(SERIALIZER_BUILDER)
+ .withTimestampProvider((k, v) -> {
+ try {
+ return deviceClockService.getTimestamp(k.deviceId());
+ } catch (IllegalStateException e) {
+ return null;
+ }
+ }).build();
+
+ devicePortStats = storageService.<DeviceId, Map<PortNumber, PortStatistics>>eventuallyConsistentMapBuilder()
+ .withName("onos-port-stats")
+ .withSerializer(SERIALIZER_BUILDER)
+ .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withTombstonesDisabled()
+ .build();
+
+ devicePortDeltaStats = storageService.<DeviceId, Map<PortNumber, PortStatistics>>
+ eventuallyConsistentMapBuilder()
+ .withName("onos-port-stats-delta")
+ .withSerializer(SERIALIZER_BUILDER)
+ .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withTombstonesDisabled()
+ .build();
+
+ clusterCommunicator.addSubscriber(DEVICE_INJECTED,
+ SERIALIZER::decode,
+ this::injectDevice,
+ SERIALIZER::encode,
+ SharedExecutors.getPoolThreadExecutor());
+
+ clusterCommunicator.addSubscriber(PORT_INJECTED,
+ SERIALIZER::decode,
+ this::injectPort,
+ SERIALIZER::encode,
+ SharedExecutors.getPoolThreadExecutor());
+
+ availableDevices = storageService.<DeviceId>setBuilder()
+ .withName("onos-online-devices")
+ .withSerializer(Serializer.using(KryoNamespaces.API))
+ .withPartitionsDisabled()
+ .withRelaxedReadConsistency()
+ .build();
+
+ deviceDescriptions.addListener(deviceUpdateListener);
+ portDescriptions.addListener(portUpdateListener);
+ devicePortStats.addListener(portStatsListener);
+ availableDevices.addListener(deviceStatusTracker);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ devicePortStats.removeListener(portStatsListener);
+ deviceDescriptions.removeListener(deviceUpdateListener);
+ portDescriptions.removeListener(portUpdateListener);
+ availableDevices.removeListener(deviceStatusTracker);
+ devicePortStats.destroy();
+ devicePortDeltaStats.destroy();
+ deviceDescriptions.destroy();
+ portDescriptions.destroy();
+ devices.clear();
+ devicePorts.clear();
+ clusterCommunicator.removeSubscriber(DEVICE_INJECTED);
+ clusterCommunicator.removeSubscriber(PORT_INJECTED);
+ log.info("Stopped");
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return devices.values();
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return devices.size();
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return devices.get(deviceId);
+ }
+
+ @Override
+ public DeviceEvent createOrUpdateDevice(ProviderId providerId,
+ DeviceId deviceId,
+ DeviceDescription deviceDescription) {
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ if (localNodeId.equals(master)) {
+ deviceDescriptions.put(new DeviceKey(providerId, deviceId), deviceDescription);
+ return refreshDeviceCache(providerId, deviceId);
+ } else {
+ DeviceInjectedEvent deviceInjectedEvent = new DeviceInjectedEvent(providerId, deviceId, deviceDescription);
+ return Futures.getUnchecked(
+ clusterCommunicator.sendAndReceive(deviceInjectedEvent,
+ DEVICE_INJECTED,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master));
+ }
+ }
+
+ private DeviceEvent refreshDeviceCache(ProviderId providerId, DeviceId deviceId) {
+ AtomicReference<DeviceEvent.Type> eventType = new AtomicReference<>();
+ Device device = devices.compute(deviceId, (k, existingDevice) -> {
+ Device newDevice = composeDevice(deviceId);
+ if (existingDevice == null) {
+ eventType.set(DEVICE_ADDED);
+ } else {
+ // We allow only certain attributes to trigger update
+ boolean propertiesChanged =
+ !Objects.equals(existingDevice.hwVersion(), newDevice.hwVersion()) ||
+ !Objects.equals(existingDevice.swVersion(), newDevice.swVersion()) ||
+ !Objects.equals(existingDevice.providerId(), newDevice.providerId());
+ boolean annotationsChanged =
+ !AnnotationsUtil.isEqual(existingDevice.annotations(), newDevice.annotations());
+
+ // Primary providers can respond to all changes, but ancillary ones
+ // should respond only to annotation changes.
+ if ((providerId.isAncillary() && annotationsChanged) ||
+ (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) {
+ boolean replaced = devices.replace(deviceId, existingDevice, newDevice);
+ verify(replaced, "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
+ providerId, existingDevice, devices.get(deviceId), newDevice);
+ eventType.set(DEVICE_UPDATED);
+ }
+ }
+ return newDevice;
+ });
+ if (eventType.get() != null && !providerId.isAncillary()) {
+ markOnline(deviceId);
+ }
+ return eventType.get() != null ? new DeviceEvent(eventType.get(), device) : null;
+ }
+
+ /**
+ * Returns the primary providerId for a device.
+ * @param deviceId device identifier
+ * @return primary providerId
+ */
+ private Set<ProviderId> getAllProviders(DeviceId deviceId) {
+ return deviceDescriptions.keySet()
+ .stream()
+ .filter(deviceKey -> deviceKey.deviceId().equals(deviceId))
+ .map(deviceKey -> deviceKey.providerId())
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns the identifier for all providers for a device.
+ * @param deviceId device identifier
+ * @return set of provider identifiers
+ */
+ private ProviderId getPrimaryProviderId(DeviceId deviceId) {
+ Set<ProviderId> allProviderIds = getAllProviders(deviceId);
+ return allProviderIds.stream()
+ .filter(p -> !p.isAncillary())
+ .findFirst()
+ .orElse(Iterables.getFirst(allProviderIds, null));
+ }
+
+ /**
+ * Returns a Device, merging descriptions from multiple Providers.
+ *
+ * @param deviceId device identifier
+ * @return Device instance
+ */
+ private Device composeDevice(DeviceId deviceId) {
+
+ ProviderId primaryProviderId = getPrimaryProviderId(deviceId);
+ DeviceDescription primaryDeviceDescription =
+ deviceDescriptions.get(new DeviceKey(primaryProviderId, deviceId));
+
+ Type type = primaryDeviceDescription.type();
+ String manufacturer = primaryDeviceDescription.manufacturer();
+ String hwVersion = primaryDeviceDescription.hwVersion();
+ String swVersion = primaryDeviceDescription.swVersion();
+ String serialNumber = primaryDeviceDescription.serialNumber();
+ ChassisId chassisId = primaryDeviceDescription.chassisId();
+ DefaultAnnotations annotations = mergeAnnotations(deviceId);
+
+ return new DefaultDevice(primaryProviderId, deviceId, type, manufacturer,
+ hwVersion, swVersion, serialNumber,
+ chassisId, annotations);
+ }
+
+ private DeviceEvent purgeDeviceCache(DeviceId deviceId) {
+ Device removedDevice = devices.remove(deviceId);
+ if (removedDevice != null) {
+ getAllProviders(deviceId).forEach(p -> deviceDescriptions.remove(new DeviceKey(p, deviceId)));
+ return new DeviceEvent(DEVICE_REMOVED, removedDevice);
+ }
+ return null;
+ }
+
+ private boolean markOnline(DeviceId deviceId) {
+ return availableDevices.add(deviceId);
+ }
+
+ @Override
+ public DeviceEvent markOffline(DeviceId deviceId) {
+ availableDevices.remove(deviceId);
+ // set update listener will raise the event.
+ return null;
+ }
+
+ @Override
+ public List<DeviceEvent> updatePorts(ProviderId providerId,
+ DeviceId deviceId,
+ List<PortDescription> descriptions) {
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ List<DeviceEvent> deviceEvents = null;
+ if (localNodeId.equals(master)) {
+ descriptions.forEach(description -> {
+ PortKey portKey = new PortKey(providerId, deviceId, description.portNumber());
+ portDescriptions.put(portKey, description);
+ });
+ deviceEvents = refreshDevicePortCache(providerId, deviceId, Optional.empty());
+ } else {
+ if (master == null) {
+ return Collections.emptyList();
+ }
+ PortInjectedEvent portInjectedEvent = new PortInjectedEvent(providerId, deviceId, descriptions);
+ deviceEvents = Futures.getUnchecked(
+ clusterCommunicator.sendAndReceive(portInjectedEvent,
+ PORT_INJECTED,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master));
+ }
+ return deviceEvents == null ? Collections.emptyList() : deviceEvents;
+ }
+
+ private List<DeviceEvent> refreshDevicePortCache(ProviderId providerId,
+ DeviceId deviceId,
+ Optional<PortNumber> portNumber) {
+ Device device = devices.get(deviceId);
+ checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+ List<DeviceEvent> events = Lists.newArrayList();
+
+ Map<PortNumber, Port> ports = devicePorts.computeIfAbsent(deviceId, key -> Maps.newConcurrentMap());
+ List<PortDescription> descriptions = Lists.newArrayList();
+ portDescriptions.entrySet().forEach(e -> {
+ PortKey key = e.getKey();
+ PortDescription value = e.getValue();
+ if (key.deviceId().equals(deviceId) && key.providerId().equals(providerId)) {
+ if (portNumber.isPresent()) {
+ if (portNumber.get().equals(key.portNumber())) {
+ descriptions.add(value);
+ }
+ } else {
+ descriptions.add(value);
+ }
+ }
+ });
+
+ for (PortDescription description : descriptions) {
+ final PortNumber number = description.portNumber();
+ ports.compute(number, (k, existingPort) -> {
+ Port newPort = composePort(device, number);
+ if (existingPort == null) {
+ events.add(new DeviceEvent(PORT_ADDED, device, newPort));
+ } else {
+ if (existingPort.isEnabled() != newPort.isEnabled() ||
+ existingPort.type() != newPort.type() ||
+ existingPort.portSpeed() != newPort.portSpeed() ||
+ !AnnotationsUtil.isEqual(existingPort.annotations(), newPort.annotations())) {
+ events.add(new DeviceEvent(PORT_UPDATED, device, newPort));
+ }
+ }
+ return newPort;
+ });
+ }
+
+ return events;
+ }
+
+ /**
+ * Returns a Port, merging descriptions from multiple Providers.
+ *
+ * @param device device the port is on
+ * @param number port number
+ * @return Port instance
+ */
+ private Port composePort(Device device, PortNumber number) {
+
+ Map<ProviderId, PortDescription> descriptions = Maps.newHashMap();
+ portDescriptions.entrySet().forEach(entry -> {
+ PortKey portKey = entry.getKey();
+ if (portKey.deviceId().equals(device.id()) && portKey.portNumber().equals(number)) {
+ descriptions.put(portKey.providerId(), entry.getValue());
+ }
+ });
+ ProviderId primary = getPrimaryProviderId(device.id());
+ PortDescription primaryDescription = descriptions.get(primary);
+
+ // if no primary, assume not enabled
+ boolean isEnabled = false;
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ if (primaryDescription != null) {
+ isEnabled = primaryDescription.isEnabled();
+ annotations = merge(annotations, primaryDescription.annotations());
+ }
+ Port updated = null;
+ for (Entry<ProviderId, PortDescription> e : descriptions.entrySet()) {
+ if (e.getKey().equals(primary)) {
+ continue;
+ }
+ annotations = merge(annotations, e.getValue().annotations());
+ updated = buildTypedPort(device, number, isEnabled, e.getValue(), annotations);
+ }
+ if (primaryDescription == null) {
+ return updated == null ? new DefaultPort(device, number, false, annotations) : updated;
+ }
+ return updated == null
+ ? buildTypedPort(device, number, isEnabled, primaryDescription, annotations)
+ : updated;
+ }
+
+ private Port buildTypedPort(Device device, PortNumber number, boolean isEnabled,
+ PortDescription description, Annotations annotations) {
+ switch (description.type()) {
+ case OMS:
+ OmsPortDescription omsDesc = (OmsPortDescription) description;
+ return new OmsPort(device, number, isEnabled, omsDesc.minFrequency(),
+ omsDesc.maxFrequency(), omsDesc.grid(), annotations);
+ case OCH:
+ OchPortDescription ochDesc = (OchPortDescription) description;
+ return new OchPort(device, number, isEnabled, ochDesc.signalType(),
+ ochDesc.isTunable(), ochDesc.lambda(), annotations);
+ case ODUCLT:
+ OduCltPortDescription oduDesc = (OduCltPortDescription) description;
+ return new OduCltPort(device, number, isEnabled, oduDesc.signalType(), annotations);
+ default:
+ return new DefaultPort(device, number, isEnabled, description.type(),
+ description.portSpeed(), annotations);
+ }
+ }
+
+ @Override
+ public DeviceEvent updatePortStatus(ProviderId providerId,
+ DeviceId deviceId,
+ PortDescription portDescription) {
+ portDescriptions.put(new PortKey(providerId, deviceId, portDescription.portNumber()), portDescription);
+ List<DeviceEvent> events =
+ refreshDevicePortCache(providerId, deviceId, Optional.of(portDescription.portNumber()));
+ return Iterables.getFirst(events, null);
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ return ImmutableList.copyOf(devicePorts.getOrDefault(deviceId, Maps.newHashMap()).values());
+ }
+
+ @Override
+ public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ return devicePorts.getOrDefault(deviceId, Maps.newHashMap()).get(portNumber);
+ }
+
+ @Override
+ public DeviceEvent updatePortStatistics(ProviderId providerId,
+ DeviceId deviceId,
+ Collection<PortStatistics> newStatsCollection) {
+
+ Map<PortNumber, PortStatistics> prvStatsMap = devicePortStats.get(deviceId);
+ Map<PortNumber, PortStatistics> newStatsMap = Maps.newHashMap();
+ Map<PortNumber, PortStatistics> deltaStatsMap = Maps.newHashMap();
+
+ if (prvStatsMap != null) {
+ for (PortStatistics newStats : newStatsCollection) {
+ PortNumber port = PortNumber.portNumber(newStats.port());
+ PortStatistics prvStats = prvStatsMap.get(port);
+ DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
+ PortStatistics deltaStats = builder.build();
+ if (prvStats != null) {
+ deltaStats = calcDeltaStats(deviceId, prvStats, newStats);
+ }
+ deltaStatsMap.put(port, deltaStats);
+ newStatsMap.put(port, newStats);
+ }
+ } else {
+ for (PortStatistics newStats : newStatsCollection) {
+ PortNumber port = PortNumber.portNumber(newStats.port());
+ newStatsMap.put(port, newStats);
+ }
+ }
+ devicePortDeltaStats.put(deviceId, deltaStatsMap);
+ devicePortStats.put(deviceId, newStatsMap);
+ // DeviceEvent returns null because of InternalPortStatsListener usage
+ return null;
+ }
+
+ /**
+ * Calculate delta statistics by subtracting previous from new statistics.
+ *
+ * @param deviceId device indentifier
+ * @param prvStats previous port statistics
+ * @param newStats new port statistics
+ * @return PortStatistics
+ */
+ public PortStatistics calcDeltaStats(DeviceId deviceId, PortStatistics prvStats, PortStatistics newStats) {
+ // calculate time difference
+ long deltaStatsSec, deltaStatsNano;
+ if (newStats.durationNano() < prvStats.durationNano()) {
+ deltaStatsNano = newStats.durationNano() - prvStats.durationNano() + TimeUnit.SECONDS.toNanos(1);
+ deltaStatsSec = newStats.durationSec() - prvStats.durationSec() - 1L;
+ } else {
+ deltaStatsNano = newStats.durationNano() - prvStats.durationNano();
+ deltaStatsSec = newStats.durationSec() - prvStats.durationSec();
+ }
+ DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
+ DefaultPortStatistics deltaStats = builder.setDeviceId(deviceId)
+ .setPort(newStats.port())
+ .setPacketsReceived(newStats.packetsReceived() - prvStats.packetsReceived())
+ .setPacketsSent(newStats.packetsSent() - prvStats.packetsSent())
+ .setBytesReceived(newStats.bytesReceived() - prvStats.bytesReceived())
+ .setBytesSent(newStats.bytesSent() - prvStats.bytesSent())
+ .setPacketsRxDropped(newStats.packetsRxDropped() - prvStats.packetsRxDropped())
+ .setPacketsTxDropped(newStats.packetsTxDropped() - prvStats.packetsTxDropped())
+ .setPacketsRxErrors(newStats.packetsRxErrors() - prvStats.packetsRxErrors())
+ .setPacketsTxErrors(newStats.packetsTxErrors() - prvStats.packetsTxErrors())
+ .setDurationSec(deltaStatsSec)
+ .setDurationNano(deltaStatsNano)
+ .build();
+ return deltaStats;
+ }
+
+ @Override
+ public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortDeltaStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return availableDevices.contains(deviceId);
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return Iterables.filter(Iterables.transform(availableDevices, devices::get), d -> d != null);
+ }
+
+ @Override
+ public DeviceEvent removeDevice(DeviceId deviceId) {
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ // if there exist a master, forward
+ // if there is no master, try to become one and process
+ boolean relinquishAtEnd = false;
+ if (master == null) {
+ final MastershipRole myRole = mastershipService.getLocalRole(deviceId);
+ if (myRole != MastershipRole.NONE) {
+ relinquishAtEnd = true;
+ }
+ log.debug("Temporarily requesting role for {} to remove", deviceId);
+ MastershipRole role = Futures.getUnchecked(mastershipService.requestRoleFor(deviceId));
+ if (role == MastershipRole.MASTER) {
+ master = localNodeId;
+ }
+ }
+
+ if (!localNodeId.equals(master)) {
+ log.debug("{} has control of {}, forwarding remove request",
+ master, deviceId);
+
+ clusterCommunicator.unicast(deviceId, DEVICE_REMOVE_REQ, SERIALIZER::encode, master)
+ .whenComplete((r, e) -> {
+ if (e != null) {
+ log.error("Failed to forward {} remove request to its master", deviceId, e);
+ }
+ });
+ return null;
+ }
+
+ // I have control..
+ DeviceEvent event = null;
+ final DeviceKey deviceKey = new DeviceKey(getPrimaryProviderId(deviceId), deviceId);
+ DeviceDescription removedDeviceDescription =
+ deviceDescriptions.remove(deviceKey);
+ if (removedDeviceDescription != null) {
+ event = purgeDeviceCache(deviceId);
+ }
+
+ if (relinquishAtEnd) {
+ log.debug("Relinquishing temporary role acquired for {}", deviceId);
+ mastershipService.relinquishMastership(deviceId);
+ }
+ return event;
+ }
+
+ private DeviceEvent injectDevice(DeviceInjectedEvent event) {
+ return createOrUpdateDevice(event.providerId(), event.deviceId(), event.deviceDescription());
+ }
+
+ private List<DeviceEvent> injectPort(PortInjectedEvent event) {
+ return updatePorts(event.providerId(), event.deviceId(), event.portDescriptions());
+ }
+
+ private DefaultAnnotations mergeAnnotations(DeviceId deviceId) {
+ ProviderId primaryProviderId = getPrimaryProviderId(deviceId);
+ DeviceDescription primaryDeviceDescription =
+ deviceDescriptions.get(new DeviceKey(primaryProviderId, deviceId));
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ annotations = merge(annotations, primaryDeviceDescription.annotations());
+ for (ProviderId providerId : getAllProviders(deviceId)) {
+ if (!providerId.equals(primaryProviderId)) {
+ annotations = merge(annotations,
+ deviceDescriptions.get(new DeviceKey(providerId, deviceId)).annotations());
+ }
+ }
+ return annotations;
+ }
+
+ private class InternalDeviceStatusTracker implements SetEventListener<DeviceId> {
+ @Override
+ public void event(SetEvent<DeviceId> event) {
+ final DeviceId deviceId = event.entry();
+ final Device device = devices.get(deviceId);
+ if (device != null) {
+ notifyDelegate(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device));
+ } else {
+ pendingAvailableChangeUpdates.add(deviceId);
+ }
+ }
+ }
+
+ private class InternalDeviceChangeEventListener
+ implements EventuallyConsistentMapListener<DeviceKey, DeviceDescription> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<DeviceKey, DeviceDescription> event) {
+ DeviceId deviceId = event.key().deviceId();
+ ProviderId providerId = event.key().providerId();
+ if (event.type() == PUT) {
+ notifyDelegate(refreshDeviceCache(providerId, deviceId));
+ if (pendingAvailableChangeUpdates.remove(deviceId)) {
+ notifyDelegate(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, devices.get(deviceId)));
+ }
+ } else if (event.type() == REMOVE) {
+ notifyDelegate(purgeDeviceCache(deviceId));
+ }
+ }
+ }
+
+ private class InternalPortChangeEventListener
+ implements EventuallyConsistentMapListener<PortKey, PortDescription> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<PortKey, PortDescription> event) {
+ DeviceId deviceId = event.key().deviceId();
+ ProviderId providerId = event.key().providerId();
+ PortNumber portNumber = event.key().portNumber();
+ if (event.type() == PUT) {
+ if (devices.containsKey(deviceId)) {
+ List<DeviceEvent> events = refreshDevicePortCache(providerId, deviceId, Optional.of(portNumber));
+ for (DeviceEvent deviceEvent : events) {
+ notifyDelegate(deviceEvent);
+ }
+ }
+ } else if (event.type() == REMOVE) {
+ log.warn("Unexpected port removed event");
+ }
+ }
+ }
+
+ private class InternalPortStatsListener
+ implements EventuallyConsistentMapListener<DeviceId, Map<PortNumber, PortStatistics>> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<DeviceId, Map<PortNumber, PortStatistics>> event) {
+ if (event.type() == PUT) {
+ Device device = devices.get(event.key());
+ if (device != null) {
+ delegate.notify(new DeviceEvent(PORT_STATS_UPDATED, device));
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java
new file mode 100644
index 00000000..63456433
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java
@@ -0,0 +1,1670 @@
+/*
+ * 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.store.device.impl;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.lang3.RandomUtils;
+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.ChassisId;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.NewConcurrentHashMap;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.AnnotationsUtil;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+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.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.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceStore;
+import org.onosproject.net.device.DeviceStoreDelegate;
+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.onosproject.net.device.PortStatistics;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.impl.Timestamped;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.serializers.custom.DistributedStoreSerializers;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.MultiValuedTimestamp;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+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 static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.base.Verify.verify;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onlab.util.Tools.minPriority;
+import static org.onosproject.cluster.ControllerNodeToNodeId.toNodeId;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.onosproject.store.device.impl.GossipDeviceStoreMessageSubjects.*;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of infrastructure devices using gossip protocol to distribute
+ * information.
+ */
+@Component(immediate = true)
+@Service
+public class GossipDeviceStore
+ extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
+ implements DeviceStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
+ // Timeout in milliseconds to process device or ports on remote master node
+ private static final int REMOTE_MASTER_TIMEOUT = 1000;
+
+ // innerMap is used to lock a Device, thus instance should never be replaced.
+ // collection of Description given from various providers
+ private final ConcurrentMap<DeviceId, Map<ProviderId, DeviceDescriptions>>
+ deviceDescs = Maps.newConcurrentMap();
+
+ // cache of Device and Ports generated by compositing descriptions from providers
+ private final ConcurrentMap<DeviceId, Device> devices = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = Maps.newConcurrentMap();
+
+ private EventuallyConsistentMap<DeviceId, Map<PortNumber, PortStatistics>> devicePortStats;
+ private EventuallyConsistentMap<DeviceId, Map<PortNumber, PortStatistics>> devicePortDeltaStats;
+ private final EventuallyConsistentMapListener<DeviceId, Map<PortNumber, PortStatistics>>
+ portStatsListener = new InternalPortStatsListener();
+
+ // to be updated under Device lock
+ private final Map<DeviceId, Timestamp> offline = Maps.newHashMap();
+ private final Map<DeviceId, Timestamp> removalRequest = Maps.newHashMap();
+
+ // available(=UP) devices
+ private final Set<DeviceId> availableDevices = Sets.newConcurrentHashSet();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceClockService deviceClockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipTermService termService;
+
+
+ protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(DistributedStoreSerializers.STORE_COMMON)
+ .nextId(DistributedStoreSerializers.STORE_CUSTOM_BEGIN)
+ .register(new InternalDeviceEventSerializer(), InternalDeviceEvent.class)
+ .register(new InternalDeviceOfflineEventSerializer(), InternalDeviceOfflineEvent.class)
+ .register(InternalDeviceRemovedEvent.class)
+ .register(new InternalPortEventSerializer(), InternalPortEvent.class)
+ .register(new InternalPortStatusEventSerializer(), InternalPortStatusEvent.class)
+ .register(DeviceAntiEntropyAdvertisement.class)
+ .register(DeviceFragmentId.class)
+ .register(PortFragmentId.class)
+ .register(DeviceInjectedEvent.class)
+ .register(PortInjectedEvent.class)
+ .build();
+ }
+ };
+
+ private ExecutorService executor;
+
+ private ScheduledExecutorService backgroundExecutor;
+
+ // TODO make these anti-entropy parameters configurable
+ private long initialDelaySec = 5;
+ private long periodSec = 5;
+
+ @Activate
+ public void activate() {
+ executor = Executors.newCachedThreadPool(groupedThreads("onos/device", "fg-%d"));
+
+ backgroundExecutor =
+ newSingleThreadScheduledExecutor(minPriority(groupedThreads("onos/device", "bg-%d")));
+
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.DEVICE_UPDATE, new InternalDeviceEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.DEVICE_OFFLINE,
+ new InternalDeviceOfflineEventListener(),
+ executor);
+ clusterCommunicator.addSubscriber(DEVICE_REMOVE_REQ,
+ new InternalRemoveRequestListener(),
+ executor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.DEVICE_REMOVED, new InternalDeviceRemovedEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.PORT_UPDATE, new InternalPortEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.PORT_STATUS_UPDATE, new InternalPortStatusEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.DEVICE_ADVERTISE,
+ new InternalDeviceAdvertisementListener(),
+ backgroundExecutor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.DEVICE_INJECTED, new DeviceInjectedEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipDeviceStoreMessageSubjects.PORT_INJECTED, new PortInjectedEventListener(), executor);
+
+ // start anti-entropy thread
+ backgroundExecutor.scheduleAtFixedRate(new SendAdvertisementTask(),
+ initialDelaySec, periodSec, TimeUnit.SECONDS);
+
+ // Create a distributed map for port stats.
+ KryoNamespace.Builder deviceDataSerializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(DefaultPortStatistics.class)
+ .register(DeviceId.class)
+ .register(MultiValuedTimestamp.class)
+ .register(WallClockTimestamp.class);
+
+ devicePortStats = storageService.<DeviceId, Map<PortNumber, PortStatistics>>eventuallyConsistentMapBuilder()
+ .withName("port-stats")
+ .withSerializer(deviceDataSerializer)
+ .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withTombstonesDisabled()
+ .build();
+ devicePortDeltaStats = storageService.<DeviceId, Map<PortNumber, PortStatistics>>
+ eventuallyConsistentMapBuilder()
+ .withName("port-stats-delta")
+ .withSerializer(deviceDataSerializer)
+ .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withTombstonesDisabled()
+ .build();
+ devicePortStats.addListener(portStatsListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ devicePortStats.destroy();
+ devicePortDeltaStats.destroy();
+ executor.shutdownNow();
+
+ backgroundExecutor.shutdownNow();
+ try {
+ if (!backgroundExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
+ log.error("Timeout during executor shutdown");
+ }
+ } catch (InterruptedException e) {
+ log.error("Error during executor shutdown", e);
+ }
+
+ deviceDescs.clear();
+ devices.clear();
+ devicePorts.clear();
+ availableDevices.clear();
+ log.info("Stopped");
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return devices.size();
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return Collections.unmodifiableCollection(devices.values());
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return FluentIterable.from(getDevices())
+ .filter(input -> isAvailable(input.id()));
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ return devices.get(deviceId);
+ }
+
+ @Override
+ public synchronized DeviceEvent createOrUpdateDevice(ProviderId providerId,
+ DeviceId deviceId,
+ DeviceDescription deviceDescription) {
+ NodeId localNode = clusterService.getLocalNode().id();
+ NodeId deviceNode = mastershipService.getMasterFor(deviceId);
+
+ // Process device update only if we're the master,
+ // otherwise signal the actual master.
+ DeviceEvent deviceEvent = null;
+ if (localNode.equals(deviceNode)) {
+
+ final Timestamp newTimestamp = deviceClockService.getTimestamp(deviceId);
+ final Timestamped<DeviceDescription> deltaDesc = new Timestamped<>(deviceDescription, newTimestamp);
+ final Timestamped<DeviceDescription> mergedDesc;
+ final Map<ProviderId, DeviceDescriptions> device = getOrCreateDeviceDescriptionsMap(deviceId);
+
+ synchronized (device) {
+ deviceEvent = createOrUpdateDeviceInternal(providerId, deviceId, deltaDesc);
+ mergedDesc = device.get(providerId).getDeviceDesc();
+ }
+
+ if (deviceEvent != null) {
+ log.debug("Notifying peers of a device update topology event for providerId: {} and deviceId: {}",
+ providerId, deviceId);
+ notifyPeers(new InternalDeviceEvent(providerId, deviceId, mergedDesc));
+ }
+
+ } else {
+ // FIXME Temporary hack for NPE (ONOS-1171).
+ // Proper fix is to implement forwarding to master on ConfigProvider
+ // redo ONOS-490
+ if (deviceNode == null) {
+ // silently ignore
+ return null;
+ }
+
+
+ DeviceInjectedEvent deviceInjectedEvent = new DeviceInjectedEvent(
+ providerId, deviceId, deviceDescription);
+
+ // TODO check unicast return value
+ clusterCommunicator.unicast(deviceInjectedEvent, DEVICE_INJECTED, SERIALIZER::encode, deviceNode);
+ /* error log:
+ log.warn("Failed to process injected device id: {} desc: {} " +
+ "(cluster messaging failed: {})",
+ deviceId, deviceDescription, e);
+ */
+ }
+
+ return deviceEvent;
+ }
+
+ private DeviceEvent createOrUpdateDeviceInternal(ProviderId providerId,
+ DeviceId deviceId,
+ Timestamped<DeviceDescription> deltaDesc) {
+
+ // Collection of DeviceDescriptions for a Device
+ Map<ProviderId, DeviceDescriptions> device
+ = getOrCreateDeviceDescriptionsMap(deviceId);
+
+ synchronized (device) {
+ // locking per device
+
+ if (isDeviceRemoved(deviceId, deltaDesc.timestamp())) {
+ log.debug("Ignoring outdated event: {}", deltaDesc);
+ return null;
+ }
+
+ DeviceDescriptions descs = getOrCreateProviderDeviceDescriptions(device, providerId, deltaDesc);
+
+ final Device oldDevice = devices.get(deviceId);
+ final Device newDevice;
+
+ if (deltaDesc == descs.getDeviceDesc() ||
+ deltaDesc.isNewer(descs.getDeviceDesc())) {
+ // on new device or valid update
+ descs.putDeviceDesc(deltaDesc);
+ newDevice = composeDevice(deviceId, device);
+ } else {
+ // outdated event, ignored.
+ return null;
+ }
+ if (oldDevice == null) {
+ // ADD
+ return createDevice(providerId, newDevice, deltaDesc.timestamp());
+ } else {
+ // UPDATE or ignore (no change or stale)
+ return updateDevice(providerId, oldDevice, newDevice, deltaDesc.timestamp());
+ }
+ }
+ }
+
+ // Creates the device and returns the appropriate event if necessary.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent createDevice(ProviderId providerId,
+ Device newDevice, Timestamp timestamp) {
+
+ // update composed device cache
+ Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice);
+ verify(oldDevice == null,
+ "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
+ providerId, oldDevice, newDevice);
+
+ if (!providerId.isAncillary()) {
+ markOnline(newDevice.id(), timestamp);
+ }
+
+ return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null);
+ }
+
+ // Updates the device and returns the appropriate event if necessary.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent updateDevice(ProviderId providerId,
+ Device oldDevice,
+ Device newDevice, Timestamp newTimestamp) {
+ // We allow only certain attributes to trigger update
+ boolean propertiesChanged =
+ !Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
+ !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
+ !Objects.equals(oldDevice.providerId(), newDevice.providerId());
+ boolean annotationsChanged =
+ !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations());
+
+ // Primary providers can respond to all changes, but ancillary ones
+ // should respond only to annotation changes.
+ if ((providerId.isAncillary() && annotationsChanged) ||
+ (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) {
+ boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
+ if (!replaced) {
+ verify(replaced,
+ "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
+ providerId, oldDevice, devices.get(newDevice.id())
+ , newDevice);
+ }
+ if (!providerId.isAncillary()) {
+ boolean wasOnline = availableDevices.contains(newDevice.id());
+ markOnline(newDevice.id(), newTimestamp);
+ if (!wasOnline) {
+ notifyDelegateIfNotNull(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null));
+ }
+ }
+
+ return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null);
+ }
+ return null;
+ }
+
+ @Override
+ public DeviceEvent markOffline(DeviceId deviceId) {
+ final Timestamp timestamp = deviceClockService.getTimestamp(deviceId);
+ final DeviceEvent event = markOfflineInternal(deviceId, timestamp);
+ if (event != null) {
+ log.debug("Notifying peers of a device offline topology event for deviceId: {} {}",
+ deviceId, timestamp);
+ notifyPeers(new InternalDeviceOfflineEvent(deviceId, timestamp));
+ }
+ return event;
+ }
+
+ private DeviceEvent markOfflineInternal(DeviceId deviceId, Timestamp timestamp) {
+
+ Map<ProviderId, DeviceDescriptions> providerDescs
+ = getOrCreateDeviceDescriptionsMap(deviceId);
+
+ // locking device
+ synchronized (providerDescs) {
+
+ // accept off-line if given timestamp is newer than
+ // the latest Timestamp from Primary provider
+ DeviceDescriptions primDescs = getPrimaryDescriptions(providerDescs);
+ Timestamp lastTimestamp = primDescs.getLatestTimestamp();
+ if (timestamp.compareTo(lastTimestamp) <= 0) {
+ // outdated event ignore
+ return null;
+ }
+
+ offline.put(deviceId, timestamp);
+
+ Device device = devices.get(deviceId);
+ if (device == null) {
+ return null;
+ }
+ boolean removed = availableDevices.remove(deviceId);
+ if (removed) {
+ return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Marks the device as available if the given timestamp is not outdated,
+ * compared to the time the device has been marked offline.
+ *
+ * @param deviceId identifier of the device
+ * @param timestamp of the event triggering this change.
+ * @return true if availability change request was accepted and changed the state
+ */
+ // Guarded by deviceDescs value (=Device lock)
+ private boolean markOnline(DeviceId deviceId, Timestamp timestamp) {
+ // accept on-line if given timestamp is newer than
+ // the latest offline request Timestamp
+ Timestamp offlineTimestamp = offline.get(deviceId);
+ if (offlineTimestamp == null ||
+ offlineTimestamp.compareTo(timestamp) < 0) {
+
+ offline.remove(deviceId);
+ return availableDevices.add(deviceId);
+ }
+ return false;
+ }
+
+ @Override
+ public synchronized List<DeviceEvent> updatePorts(ProviderId providerId,
+ DeviceId deviceId,
+ List<PortDescription> portDescriptions) {
+
+ NodeId localNode = clusterService.getLocalNode().id();
+ // TODO: It might be negligible, but this will have negative impact to topology discovery performance,
+ // since it will trigger distributed store read.
+ // Also, it'll probably be better if side-way communication happened on ConfigurationProvider, etc.
+ // outside Device subsystem. so that we don't have to modify both Device and Link stores.
+ // If we don't care much about topology performance, then it might be OK.
+ NodeId deviceNode = mastershipService.getMasterFor(deviceId);
+
+ // Process port update only if we're the master of the device,
+ // otherwise signal the actual master.
+ List<DeviceEvent> deviceEvents = null;
+ if (localNode.equals(deviceNode)) {
+
+ final Timestamp newTimestamp;
+ try {
+ newTimestamp = deviceClockService.getTimestamp(deviceId);
+ } catch (IllegalStateException e) {
+ log.info("Timestamp was not available for device {}", deviceId);
+ log.debug(" discarding {}", portDescriptions);
+ // Failed to generate timestamp.
+
+ // Possible situation:
+ // Device connected and became master for short period of time,
+ // but lost mastership before this instance had the chance to
+ // retrieve term information.
+
+ // Information dropped here is expected to be recoverable by
+ // device probing after mastership change
+
+ return Collections.emptyList();
+ }
+ log.debug("timestamp for {} {}", deviceId, newTimestamp);
+
+ final Timestamped<List<PortDescription>> timestampedInput
+ = new Timestamped<>(portDescriptions, newTimestamp);
+ final Timestamped<List<PortDescription>> merged;
+
+ final Map<ProviderId, DeviceDescriptions> device = getOrCreateDeviceDescriptionsMap(deviceId);
+
+ synchronized (device) {
+ deviceEvents = updatePortsInternal(providerId, deviceId, timestampedInput);
+ final DeviceDescriptions descs = device.get(providerId);
+ List<PortDescription> mergedList =
+ FluentIterable.from(portDescriptions)
+ .transform(new Function<PortDescription, PortDescription>() {
+ @Override
+ public PortDescription apply(PortDescription input) {
+ // lookup merged port description
+ return descs.getPortDesc(input.portNumber()).value();
+ }
+ }).toList();
+ merged = new Timestamped<>(mergedList, newTimestamp);
+ }
+
+ if (!deviceEvents.isEmpty()) {
+ log.debug("Notifying peers of a ports update topology event for providerId: {} and deviceId: {}",
+ providerId, deviceId);
+ notifyPeers(new InternalPortEvent(providerId, deviceId, merged));
+ }
+
+ } else {
+ // FIXME Temporary hack for NPE (ONOS-1171).
+ // Proper fix is to implement forwarding to master on ConfigProvider
+ // redo ONOS-490
+ if (deviceNode == null) {
+ // silently ignore
+ return Collections.emptyList();
+ }
+
+ PortInjectedEvent portInjectedEvent = new PortInjectedEvent(providerId, deviceId, portDescriptions);
+
+ //TODO check unicast return value
+ clusterCommunicator.unicast(portInjectedEvent, PORT_INJECTED, SERIALIZER::encode, deviceNode);
+ /* error log:
+ log.warn("Failed to process injected ports of device id: {} " +
+ "(cluster messaging failed: {})",
+ deviceId, e);
+ */
+ }
+
+ return deviceEvents == null ? Collections.emptyList() : deviceEvents;
+ }
+
+ private List<DeviceEvent> updatePortsInternal(ProviderId providerId,
+ DeviceId deviceId,
+ Timestamped<List<PortDescription>> portDescriptions) {
+
+ Device device = devices.get(deviceId);
+ checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+
+ Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+ checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+ List<DeviceEvent> events = new ArrayList<>();
+ synchronized (descsMap) {
+
+ if (isDeviceRemoved(deviceId, portDescriptions.timestamp())) {
+ log.debug("Ignoring outdated events: {}", portDescriptions);
+ return Collections.emptyList();
+ }
+
+ DeviceDescriptions descs = descsMap.get(providerId);
+ // every provider must provide DeviceDescription.
+ checkArgument(descs != null,
+ "Device description for Device ID %s from Provider %s was not found",
+ deviceId, providerId);
+
+ Map<PortNumber, Port> ports = getPortMap(deviceId);
+
+ final Timestamp newTimestamp = portDescriptions.timestamp();
+
+ // Add new ports
+ Set<PortNumber> processed = new HashSet<>();
+ for (PortDescription portDescription : portDescriptions.value()) {
+ final PortNumber number = portDescription.portNumber();
+ processed.add(number);
+
+ final Port oldPort = ports.get(number);
+ final Port newPort;
+
+
+ final Timestamped<PortDescription> existingPortDesc = descs.getPortDesc(number);
+ if (existingPortDesc == null ||
+ newTimestamp.compareTo(existingPortDesc.timestamp()) >= 0) {
+ // on new port or valid update
+ // update description
+ descs.putPortDesc(new Timestamped<>(portDescription,
+ portDescriptions.timestamp()));
+ newPort = composePort(device, number, descsMap);
+ } else {
+ // outdated event, ignored.
+ continue;
+ }
+
+ events.add(oldPort == null ?
+ createPort(device, newPort, ports) :
+ updatePort(device, oldPort, newPort, ports));
+ }
+
+ events.addAll(pruneOldPorts(device, ports, processed));
+ }
+ return FluentIterable.from(events).filter(notNull()).toList();
+ }
+
+ // Creates a new port based on the port description adds it to the map and
+ // Returns corresponding event.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent createPort(Device device, Port newPort,
+ Map<PortNumber, Port> ports) {
+ ports.put(newPort.number(), newPort);
+ return new DeviceEvent(PORT_ADDED, device, newPort);
+ }
+
+ // Checks if the specified port requires update and if so, it replaces the
+ // existing entry in the map and returns corresponding event.
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceEvent updatePort(Device device, Port oldPort,
+ Port newPort,
+ Map<PortNumber, Port> ports) {
+ if (oldPort.isEnabled() != newPort.isEnabled() ||
+ oldPort.type() != newPort.type() ||
+ oldPort.portSpeed() != newPort.portSpeed() ||
+ !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
+ ports.put(oldPort.number(), newPort);
+ return new DeviceEvent(PORT_UPDATED, device, newPort);
+ }
+ return null;
+ }
+
+ // Prunes the specified list of ports based on which ports are in the
+ // processed list and returns list of corresponding events.
+ // Guarded by deviceDescs value (=Device lock)
+ private List<DeviceEvent> pruneOldPorts(Device device,
+ Map<PortNumber, Port> ports,
+ Set<PortNumber> processed) {
+ List<DeviceEvent> events = new ArrayList<>();
+ Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<PortNumber, Port> e = iterator.next();
+ PortNumber portNumber = e.getKey();
+ if (!processed.contains(portNumber)) {
+ events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue()));
+ iterator.remove();
+ }
+ }
+ return events;
+ }
+
+ // Gets the map of ports for the specified device; if one does not already
+ // exist, it creates and registers a new one.
+ private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
+ return createIfAbsentUnchecked(devicePorts, deviceId,
+ NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
+ }
+
+ private Map<ProviderId, DeviceDescriptions> getOrCreateDeviceDescriptionsMap(
+ DeviceId deviceId) {
+ Map<ProviderId, DeviceDescriptions> r;
+ r = deviceDescs.get(deviceId);
+ if (r == null) {
+ r = new HashMap<>();
+ final Map<ProviderId, DeviceDescriptions> concurrentlyAdded;
+ concurrentlyAdded = deviceDescs.putIfAbsent(deviceId, r);
+ if (concurrentlyAdded != null) {
+ r = concurrentlyAdded;
+ }
+ }
+ return r;
+ }
+
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceDescriptions getOrCreateProviderDeviceDescriptions(
+ Map<ProviderId, DeviceDescriptions> device,
+ ProviderId providerId, Timestamped<DeviceDescription> deltaDesc) {
+ synchronized (device) {
+ DeviceDescriptions r = device.get(providerId);
+ if (r == null) {
+ r = new DeviceDescriptions(deltaDesc);
+ device.put(providerId, r);
+ }
+ return r;
+ }
+ }
+
+ @Override
+ public synchronized DeviceEvent updatePortStatus(ProviderId providerId,
+ DeviceId deviceId,
+ PortDescription portDescription) {
+ final Timestamp newTimestamp;
+ try {
+ newTimestamp = deviceClockService.getTimestamp(deviceId);
+ } catch (IllegalStateException e) {
+ log.info("Timestamp was not available for device {}", deviceId);
+ log.debug(" discarding {}", portDescription);
+ // Failed to generate timestamp. Ignoring.
+ // See updatePorts comment
+ return null;
+ }
+ final Timestamped<PortDescription> deltaDesc
+ = new Timestamped<>(portDescription, newTimestamp);
+ final DeviceEvent event;
+ final Timestamped<PortDescription> mergedDesc;
+ final Map<ProviderId, DeviceDescriptions> device = getOrCreateDeviceDescriptionsMap(deviceId);
+ synchronized (device) {
+ event = updatePortStatusInternal(providerId, deviceId, deltaDesc);
+ mergedDesc = device.get(providerId)
+ .getPortDesc(portDescription.portNumber());
+ }
+ if (event != null) {
+ log.debug("Notifying peers of a port status update topology event for providerId: {} and deviceId: {}",
+ providerId, deviceId);
+ notifyPeers(new InternalPortStatusEvent(providerId, deviceId, mergedDesc));
+ }
+ return event;
+ }
+
+ private DeviceEvent updatePortStatusInternal(ProviderId providerId, DeviceId deviceId,
+ Timestamped<PortDescription> deltaDesc) {
+ Device device = devices.get(deviceId);
+ checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+
+ Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+ checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+ synchronized (descsMap) {
+
+ if (isDeviceRemoved(deviceId, deltaDesc.timestamp())) {
+ log.debug("Ignoring outdated event: {}", deltaDesc);
+ return null;
+ }
+
+ DeviceDescriptions descs = descsMap.get(providerId);
+ // assuming all providers must to give DeviceDescription
+ verify(descs != null,
+ "Device description for Device ID %s from Provider %s was not found",
+ deviceId, providerId);
+
+ ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
+ final PortNumber number = deltaDesc.value().portNumber();
+ final Port oldPort = ports.get(number);
+ final Port newPort;
+
+ final Timestamped<PortDescription> existingPortDesc = descs.getPortDesc(number);
+ if (existingPortDesc == null ||
+ deltaDesc.isNewer(existingPortDesc)) {
+ // on new port or valid update
+ // update description
+ descs.putPortDesc(deltaDesc);
+ newPort = composePort(device, number, descsMap);
+ } else {
+ // same or outdated event, ignored.
+ log.trace("ignore same or outdated {} >= {}", existingPortDesc, deltaDesc);
+ return null;
+ }
+
+ if (oldPort == null) {
+ return createPort(device, newPort, ports);
+ } else {
+ return updatePort(device, oldPort, newPort, ports);
+ }
+ }
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ if (ports == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(ports.values());
+ }
+
+ @Override
+ public DeviceEvent updatePortStatistics(ProviderId providerId, DeviceId deviceId,
+ Collection<PortStatistics> newStatsCollection) {
+
+ Map<PortNumber, PortStatistics> prvStatsMap = devicePortStats.get(deviceId);
+ Map<PortNumber, PortStatistics> newStatsMap = Maps.newHashMap();
+ Map<PortNumber, PortStatistics> deltaStatsMap = Maps.newHashMap();
+
+ if (prvStatsMap != null) {
+ for (PortStatistics newStats : newStatsCollection) {
+ PortNumber port = PortNumber.portNumber(newStats.port());
+ PortStatistics prvStats = prvStatsMap.get(port);
+ DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
+ PortStatistics deltaStats = builder.build();
+ if (prvStats != null) {
+ deltaStats = calcDeltaStats(deviceId, prvStats, newStats);
+ }
+ deltaStatsMap.put(port, deltaStats);
+ newStatsMap.put(port, newStats);
+ }
+ } else {
+ for (PortStatistics newStats : newStatsCollection) {
+ PortNumber port = PortNumber.portNumber(newStats.port());
+ newStatsMap.put(port, newStats);
+ }
+ }
+ devicePortDeltaStats.put(deviceId, deltaStatsMap);
+ devicePortStats.put(deviceId, newStatsMap);
+ // DeviceEvent returns null because of InternalPortStatsListener usage
+ return null;
+ }
+
+ /**
+ * Calculate delta statistics by subtracting previous from new statistics.
+ *
+ * @param deviceId device identifier
+ * @param prvStats previous port statistics
+ * @param newStats new port statistics
+ * @return PortStatistics
+ */
+ public PortStatistics calcDeltaStats(DeviceId deviceId, PortStatistics prvStats, PortStatistics newStats) {
+ // calculate time difference
+ long deltaStatsSec, deltaStatsNano;
+ if (newStats.durationNano() < prvStats.durationNano()) {
+ deltaStatsNano = newStats.durationNano() - prvStats.durationNano() + TimeUnit.SECONDS.toNanos(1);
+ deltaStatsSec = newStats.durationSec() - prvStats.durationSec() - 1L;
+ } else {
+ deltaStatsNano = newStats.durationNano() - prvStats.durationNano();
+ deltaStatsSec = newStats.durationSec() - prvStats.durationSec();
+ }
+ DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
+ DefaultPortStatistics deltaStats = builder.setDeviceId(deviceId)
+ .setPort(newStats.port())
+ .setPacketsReceived(newStats.packetsReceived() - prvStats.packetsReceived())
+ .setPacketsSent(newStats.packetsSent() - prvStats.packetsSent())
+ .setBytesReceived(newStats.bytesReceived() - prvStats.bytesReceived())
+ .setBytesSent(newStats.bytesSent() - prvStats.bytesSent())
+ .setPacketsRxDropped(newStats.packetsRxDropped() - prvStats.packetsRxDropped())
+ .setPacketsTxDropped(newStats.packetsTxDropped() - prvStats.packetsTxDropped())
+ .setPacketsRxErrors(newStats.packetsRxErrors() - prvStats.packetsRxErrors())
+ .setPacketsTxErrors(newStats.packetsTxErrors() - prvStats.packetsTxErrors())
+ .setDurationSec(deltaStatsSec)
+ .setDurationNano(deltaStatsNano)
+ .build();
+ return deltaStats;
+ }
+
+ @Override
+ public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortDeltaStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ return ports == null ? null : ports.get(portNumber);
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return availableDevices.contains(deviceId);
+ }
+
+ @Override
+ public synchronized DeviceEvent removeDevice(DeviceId deviceId) {
+ final NodeId myId = clusterService.getLocalNode().id();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ // if there exist a master, forward
+ // if there is no master, try to become one and process
+
+ boolean relinquishAtEnd = false;
+ if (master == null) {
+ final MastershipRole myRole = mastershipService.getLocalRole(deviceId);
+ if (myRole != MastershipRole.NONE) {
+ relinquishAtEnd = true;
+ }
+ log.debug("Temporarily requesting role for {} to remove", deviceId);
+ mastershipService.requestRoleFor(deviceId);
+ MastershipTerm term = termService.getMastershipTerm(deviceId);
+ if (term != null && myId.equals(term.master())) {
+ master = myId;
+ }
+ }
+
+ if (!myId.equals(master)) {
+ log.debug("{} has control of {}, forwarding remove request",
+ master, deviceId);
+
+ // TODO check unicast return value
+ clusterCommunicator.unicast(deviceId, DEVICE_REMOVE_REQ, SERIALIZER::encode, master);
+ /* error log:
+ log.error("Failed to forward {} remove request to {}", deviceId, master, e);
+ */
+
+ // event will be triggered after master processes it.
+ return null;
+ }
+
+ // I have control..
+
+ Timestamp timestamp = deviceClockService.getTimestamp(deviceId);
+ DeviceEvent event = removeDeviceInternal(deviceId, timestamp);
+ if (event != null) {
+ log.debug("Notifying peers of a device removed topology event for deviceId: {}",
+ deviceId);
+ notifyPeers(new InternalDeviceRemovedEvent(deviceId, timestamp));
+ }
+ if (relinquishAtEnd) {
+ log.debug("Relinquishing temporary role acquired for {}", deviceId);
+ mastershipService.relinquishMastership(deviceId);
+ }
+ return event;
+ }
+
+ private DeviceEvent removeDeviceInternal(DeviceId deviceId,
+ Timestamp timestamp) {
+
+ Map<ProviderId, DeviceDescriptions> descs = getOrCreateDeviceDescriptionsMap(deviceId);
+ synchronized (descs) {
+ // accept removal request if given timestamp is newer than
+ // the latest Timestamp from Primary provider
+ DeviceDescriptions primDescs = getPrimaryDescriptions(descs);
+ Timestamp lastTimestamp = primDescs.getLatestTimestamp();
+ if (timestamp.compareTo(lastTimestamp) <= 0) {
+ // outdated event ignore
+ return null;
+ }
+ removalRequest.put(deviceId, timestamp);
+
+ Device device = devices.remove(deviceId);
+ // should DEVICE_REMOVED carry removed ports?
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ if (ports != null) {
+ ports.clear();
+ }
+ markOfflineInternal(deviceId, timestamp);
+ descs.clear();
+ return device == null ? null :
+ new DeviceEvent(DeviceEvent.Type.DEVICE_REMOVED, device, null);
+ }
+ }
+
+ /**
+ * Checks if given timestamp is superseded by removal request
+ * with more recent timestamp.
+ *
+ * @param deviceId identifier of a device
+ * @param timestampToCheck timestamp of an event to check
+ * @return true if device is already removed
+ */
+ private boolean isDeviceRemoved(DeviceId deviceId, Timestamp timestampToCheck) {
+ Timestamp removalTimestamp = removalRequest.get(deviceId);
+ if (removalTimestamp != null &&
+ removalTimestamp.compareTo(timestampToCheck) >= 0) {
+ // removalRequest is more recent
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a Device, merging description given from multiple Providers.
+ *
+ * @param deviceId device identifier
+ * @param providerDescs Collection of Descriptions from multiple providers
+ * @return Device instance
+ */
+ private Device composeDevice(DeviceId deviceId,
+ Map<ProviderId, DeviceDescriptions> providerDescs) {
+
+ checkArgument(!providerDescs.isEmpty(), "No device descriptions supplied");
+
+ ProviderId primary = pickPrimaryPID(providerDescs);
+
+ DeviceDescriptions desc = providerDescs.get(primary);
+
+ final DeviceDescription base = desc.getDeviceDesc().value();
+ Type type = base.type();
+ String manufacturer = base.manufacturer();
+ String hwVersion = base.hwVersion();
+ String swVersion = base.swVersion();
+ String serialNumber = base.serialNumber();
+ ChassisId chassisId = base.chassisId();
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ annotations = merge(annotations, base.annotations());
+
+ for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
+ if (e.getKey().equals(primary)) {
+ continue;
+ }
+ // Note: should keep track of Description timestamp in the future
+ // and only merge conflicting keys when timestamp is newer.
+ // Currently assuming there will never be a key conflict between
+ // providers
+
+ // annotation merging. not so efficient, should revisit later
+ annotations = merge(annotations, e.getValue().getDeviceDesc().value().annotations());
+ }
+
+ return new DefaultDevice(primary, deviceId, type, manufacturer,
+ hwVersion, swVersion, serialNumber,
+ chassisId, annotations);
+ }
+
+ private Port buildTypedPort(Device device, PortNumber number, boolean isEnabled,
+ PortDescription description, Annotations annotations) {
+ switch (description.type()) {
+ case OMS:
+ OmsPortDescription omsDesc = (OmsPortDescription) description;
+ return new OmsPort(device, number, isEnabled, omsDesc.minFrequency(),
+ omsDesc.maxFrequency(), omsDesc.grid(), annotations);
+ case OCH:
+ OchPortDescription ochDesc = (OchPortDescription) description;
+ return new OchPort(device, number, isEnabled, ochDesc.signalType(),
+ ochDesc.isTunable(), ochDesc.lambda(), annotations);
+ case ODUCLT:
+ OduCltPortDescription oduDesc = (OduCltPortDescription) description;
+ return new OduCltPort(device, number, isEnabled, oduDesc.signalType(), annotations);
+ default:
+ return new DefaultPort(device, number, isEnabled, description.type(),
+ description.portSpeed(), annotations);
+ }
+ }
+
+ /**
+ * Returns a Port, merging description given from multiple Providers.
+ *
+ * @param device device the port is on
+ * @param number port number
+ * @param descsMap Collection of Descriptions from multiple providers
+ * @return Port instance
+ */
+ private Port composePort(Device device, PortNumber number,
+ Map<ProviderId, DeviceDescriptions> descsMap) {
+
+ ProviderId primary = pickPrimaryPID(descsMap);
+ DeviceDescriptions primDescs = descsMap.get(primary);
+ // if no primary, assume not enabled
+ boolean isEnabled = false;
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ Timestamp newest = null;
+ final Timestamped<PortDescription> portDesc = primDescs.getPortDesc(number);
+ if (portDesc != null) {
+ isEnabled = portDesc.value().isEnabled();
+ annotations = merge(annotations, portDesc.value().annotations());
+ newest = portDesc.timestamp();
+ }
+ Port updated = null;
+ for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) {
+ if (e.getKey().equals(primary)) {
+ continue;
+ }
+ // Note: should keep track of Description timestamp in the future
+ // and only merge conflicting keys when timestamp is newer.
+ // Currently assuming there will never be a key conflict between
+ // providers
+
+ // annotation merging. not so efficient, should revisit later
+ final Timestamped<PortDescription> otherPortDesc = e.getValue().getPortDesc(number);
+ if (otherPortDesc != null) {
+ if (newest != null && newest.isNewerThan(otherPortDesc.timestamp())) {
+ continue;
+ }
+ annotations = merge(annotations, otherPortDesc.value().annotations());
+ PortDescription other = otherPortDesc.value();
+ updated = buildTypedPort(device, number, isEnabled, other, annotations);
+ newest = otherPortDesc.timestamp();
+ }
+ }
+ if (portDesc == null) {
+ return updated == null ? new DefaultPort(device, number, false, annotations) : updated;
+ }
+ PortDescription current = portDesc.value();
+ return updated == null
+ ? buildTypedPort(device, number, isEnabled, current, annotations)
+ : updated;
+ }
+
+ /**
+ * @return primary ProviderID, or randomly chosen one if none exists
+ */
+ private ProviderId pickPrimaryPID(
+ Map<ProviderId, DeviceDescriptions> providerDescs) {
+ ProviderId fallBackPrimary = null;
+ for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
+ if (!e.getKey().isAncillary()) {
+ return e.getKey();
+ } else if (fallBackPrimary == null) {
+ // pick randomly as a fallback in case there is no primary
+ fallBackPrimary = e.getKey();
+ }
+ }
+ return fallBackPrimary;
+ }
+
+ private DeviceDescriptions getPrimaryDescriptions(
+ Map<ProviderId, DeviceDescriptions> providerDescs) {
+ ProviderId pid = pickPrimaryPID(providerDescs);
+ return providerDescs.get(pid);
+ }
+
+ private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) throws IOException {
+ clusterCommunicator.unicast(event, subject, SERIALIZER::encode, recipient);
+ }
+
+ private void broadcastMessage(MessageSubject subject, Object event) {
+ clusterCommunicator.broadcast(event, subject, SERIALIZER::encode);
+ }
+
+ private void notifyPeers(InternalDeviceEvent event) {
+ broadcastMessage(GossipDeviceStoreMessageSubjects.DEVICE_UPDATE, event);
+ }
+
+ private void notifyPeers(InternalDeviceOfflineEvent event) {
+ broadcastMessage(GossipDeviceStoreMessageSubjects.DEVICE_OFFLINE, event);
+ }
+
+ private void notifyPeers(InternalDeviceRemovedEvent event) {
+ broadcastMessage(GossipDeviceStoreMessageSubjects.DEVICE_REMOVED, event);
+ }
+
+ private void notifyPeers(InternalPortEvent event) {
+ broadcastMessage(GossipDeviceStoreMessageSubjects.PORT_UPDATE, event);
+ }
+
+ private void notifyPeers(InternalPortStatusEvent event) {
+ broadcastMessage(GossipDeviceStoreMessageSubjects.PORT_STATUS_UPDATE, event);
+ }
+
+ private void notifyPeer(NodeId recipient, InternalDeviceEvent event) {
+ try {
+ unicastMessage(recipient, GossipDeviceStoreMessageSubjects.DEVICE_UPDATE, event);
+ } catch (IOException e) {
+ log.error("Failed to send" + event + " to " + recipient, e);
+ }
+ }
+
+ private void notifyPeer(NodeId recipient, InternalDeviceOfflineEvent event) {
+ try {
+ unicastMessage(recipient, GossipDeviceStoreMessageSubjects.DEVICE_OFFLINE, event);
+ } catch (IOException e) {
+ log.error("Failed to send" + event + " to " + recipient, e);
+ }
+ }
+
+ private void notifyPeer(NodeId recipient, InternalDeviceRemovedEvent event) {
+ try {
+ unicastMessage(recipient, GossipDeviceStoreMessageSubjects.DEVICE_REMOVED, event);
+ } catch (IOException e) {
+ log.error("Failed to send" + event + " to " + recipient, e);
+ }
+ }
+
+ private void notifyPeer(NodeId recipient, InternalPortEvent event) {
+ try {
+ unicastMessage(recipient, GossipDeviceStoreMessageSubjects.PORT_UPDATE, event);
+ } catch (IOException e) {
+ log.error("Failed to send" + event + " to " + recipient, e);
+ }
+ }
+
+ private void notifyPeer(NodeId recipient, InternalPortStatusEvent event) {
+ try {
+ unicastMessage(recipient, GossipDeviceStoreMessageSubjects.PORT_STATUS_UPDATE, event);
+ } catch (IOException e) {
+ log.error("Failed to send" + event + " to " + recipient, e);
+ }
+ }
+
+ private DeviceAntiEntropyAdvertisement createAdvertisement() {
+ final NodeId self = clusterService.getLocalNode().id();
+
+ final int numDevices = deviceDescs.size();
+ Map<DeviceFragmentId, Timestamp> adDevices = new HashMap<>(numDevices);
+ final int portsPerDevice = 8; // random factor to minimize reallocation
+ Map<PortFragmentId, Timestamp> adPorts = new HashMap<>(numDevices * portsPerDevice);
+ Map<DeviceId, Timestamp> adOffline = new HashMap<>(numDevices);
+
+ deviceDescs.forEach((deviceId, devDescs) -> {
+
+ // for each Device...
+ synchronized (devDescs) {
+
+ // send device offline timestamp
+ Timestamp lOffline = this.offline.get(deviceId);
+ if (lOffline != null) {
+ adOffline.put(deviceId, lOffline);
+ }
+
+ for (Entry<ProviderId, DeviceDescriptions>
+ prov : devDescs.entrySet()) {
+
+ // for each Provider Descriptions...
+ final ProviderId provId = prov.getKey();
+ final DeviceDescriptions descs = prov.getValue();
+
+ adDevices.put(new DeviceFragmentId(deviceId, provId),
+ descs.getDeviceDesc().timestamp());
+
+ for (Entry<PortNumber, Timestamped<PortDescription>>
+ portDesc : descs.getPortDescs().entrySet()) {
+
+ final PortNumber number = portDesc.getKey();
+ adPorts.put(new PortFragmentId(deviceId, provId, number),
+ portDesc.getValue().timestamp());
+ }
+ }
+ }
+ });
+
+ return new DeviceAntiEntropyAdvertisement(self, adDevices, adPorts, adOffline);
+ }
+
+ /**
+ * Responds to anti-entropy advertisement message.
+ * <p/>
+ * Notify sender about out-dated information using regular replication message.
+ * Send back advertisement to sender if not in sync.
+ *
+ * @param advertisement to respond to
+ */
+ private void handleAdvertisement(DeviceAntiEntropyAdvertisement advertisement) {
+
+ final NodeId sender = advertisement.sender();
+
+ Map<DeviceFragmentId, Timestamp> devAds = new HashMap<>(advertisement.deviceFingerPrints());
+ Map<PortFragmentId, Timestamp> portAds = new HashMap<>(advertisement.ports());
+ Map<DeviceId, Timestamp> offlineAds = new HashMap<>(advertisement.offline());
+
+ // Fragments to request
+ Collection<DeviceFragmentId> reqDevices = new ArrayList<>();
+ Collection<PortFragmentId> reqPorts = new ArrayList<>();
+
+ for (Entry<DeviceId, Map<ProviderId, DeviceDescriptions>> de : deviceDescs.entrySet()) {
+ final DeviceId deviceId = de.getKey();
+ final Map<ProviderId, DeviceDescriptions> lDevice = de.getValue();
+
+ synchronized (lDevice) {
+ // latestTimestamp across provider
+ // Note: can be null initially
+ Timestamp localLatest = offline.get(deviceId);
+
+ // handle device Ads
+ for (Entry<ProviderId, DeviceDescriptions> prov : lDevice.entrySet()) {
+ final ProviderId provId = prov.getKey();
+ final DeviceDescriptions lDeviceDescs = prov.getValue();
+
+ final DeviceFragmentId devFragId = new DeviceFragmentId(deviceId, provId);
+
+
+ Timestamped<DeviceDescription> lProvDevice = lDeviceDescs.getDeviceDesc();
+ Timestamp advDevTimestamp = devAds.get(devFragId);
+
+ if (advDevTimestamp == null || lProvDevice.isNewerThan(
+ advDevTimestamp)) {
+ // remote does not have it or outdated, suggest
+ notifyPeer(sender, new InternalDeviceEvent(provId, deviceId, lProvDevice));
+ } else if (!lProvDevice.timestamp().equals(advDevTimestamp)) {
+ // local is outdated, request
+ reqDevices.add(devFragId);
+ }
+
+ // handle port Ads
+ for (Entry<PortNumber, Timestamped<PortDescription>>
+ pe : lDeviceDescs.getPortDescs().entrySet()) {
+
+ final PortNumber num = pe.getKey();
+ final Timestamped<PortDescription> lPort = pe.getValue();
+
+ final PortFragmentId portFragId = new PortFragmentId(deviceId, provId, num);
+
+ Timestamp advPortTimestamp = portAds.get(portFragId);
+ if (advPortTimestamp == null || lPort.isNewerThan(
+ advPortTimestamp)) {
+ // remote does not have it or outdated, suggest
+ notifyPeer(sender, new InternalPortStatusEvent(provId, deviceId, lPort));
+ } else if (!lPort.timestamp().equals(advPortTimestamp)) {
+ // local is outdated, request
+ log.trace("need update {} < {}", lPort.timestamp(), advPortTimestamp);
+ reqPorts.add(portFragId);
+ }
+
+ // remove port Ad already processed
+ portAds.remove(portFragId);
+ } // end local port loop
+
+ // remove device Ad already processed
+ devAds.remove(devFragId);
+
+ // find latest and update
+ final Timestamp providerLatest = lDeviceDescs.getLatestTimestamp();
+ if (localLatest == null ||
+ providerLatest.compareTo(localLatest) > 0) {
+ localLatest = providerLatest;
+ }
+ } // end local provider loop
+
+ // checking if remote timestamp is more recent.
+ Timestamp rOffline = offlineAds.get(deviceId);
+ if (rOffline != null &&
+ rOffline.compareTo(localLatest) > 0) {
+ // remote offline timestamp suggests that the
+ // device is off-line
+ markOfflineInternal(deviceId, rOffline);
+ }
+
+ Timestamp lOffline = offline.get(deviceId);
+ if (lOffline != null && rOffline == null) {
+ // locally offline, but remote is online, suggest offline
+ notifyPeer(sender, new InternalDeviceOfflineEvent(deviceId, lOffline));
+ }
+
+ // remove device offline Ad already processed
+ offlineAds.remove(deviceId);
+ } // end local device loop
+ } // device lock
+
+ // If there is any Ads left, request them
+ log.trace("Ads left {}, {}", devAds, portAds);
+ reqDevices.addAll(devAds.keySet());
+ reqPorts.addAll(portAds.keySet());
+
+ if (reqDevices.isEmpty() && reqPorts.isEmpty()) {
+ log.trace("Nothing to request to remote peer {}", sender);
+ return;
+ }
+
+ log.debug("Need to sync {} {}", reqDevices, reqPorts);
+
+ // 2-way Anti-Entropy for now
+ try {
+ unicastMessage(sender, DEVICE_ADVERTISE, createAdvertisement());
+ } catch (IOException e) {
+ log.error("Failed to send response advertisement to " + sender, e);
+ }
+
+// Sketch of 3-way Anti-Entropy
+// DeviceAntiEntropyRequest request = new DeviceAntiEntropyRequest(self, reqDevices, reqPorts);
+// ClusterMessage message = new ClusterMessage(
+// clusterService.getLocalNode().id(),
+// GossipDeviceStoreMessageSubjects.DEVICE_REQUEST,
+// SERIALIZER.encode(request));
+//
+// try {
+// clusterCommunicator.unicast(message, advertisement.sender());
+// } catch (IOException e) {
+// log.error("Failed to send advertisement reply to "
+// + advertisement.sender(), e);
+// }
+ }
+
+ private void notifyDelegateIfNotNull(DeviceEvent event) {
+ if (event != null) {
+ notifyDelegate(event);
+ }
+ }
+
+ private final class SendAdvertisementTask implements Runnable {
+
+ @Override
+ public void run() {
+ if (Thread.currentThread().isInterrupted()) {
+ log.debug("Interrupted, quitting");
+ return;
+ }
+
+ try {
+ final NodeId self = clusterService.getLocalNode().id();
+ Set<ControllerNode> nodes = clusterService.getNodes();
+
+ ImmutableList<NodeId> nodeIds = FluentIterable.from(nodes)
+ .transform(toNodeId())
+ .toList();
+
+ if (nodeIds.size() == 1 && nodeIds.get(0).equals(self)) {
+ log.trace("No other peers in the cluster.");
+ return;
+ }
+
+ NodeId peer;
+ do {
+ int idx = RandomUtils.nextInt(0, nodeIds.size());
+ peer = nodeIds.get(idx);
+ } while (peer.equals(self));
+
+ DeviceAntiEntropyAdvertisement ad = createAdvertisement();
+
+ if (Thread.currentThread().isInterrupted()) {
+ log.debug("Interrupted, quitting");
+ return;
+ }
+
+ try {
+ unicastMessage(peer, DEVICE_ADVERTISE, ad);
+ } catch (IOException e) {
+ log.debug("Failed to send anti-entropy advertisement to {}", peer);
+ return;
+ }
+ } catch (Exception e) {
+ // catch all Exception to avoid Scheduled task being suppressed.
+ log.error("Exception thrown while sending advertisement", e);
+ }
+ }
+ }
+
+ private final class InternalDeviceEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received device update event from peer: {}", message.sender());
+ InternalDeviceEvent event = SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = event.providerId();
+ DeviceId deviceId = event.deviceId();
+ Timestamped<DeviceDescription> deviceDescription = event.deviceDescription();
+
+ try {
+ notifyDelegateIfNotNull(createOrUpdateDeviceInternal(providerId, deviceId, deviceDescription));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling device update", e);
+ }
+ }
+ }
+
+ private final class InternalDeviceOfflineEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received device offline event from peer: {}", message.sender());
+ InternalDeviceOfflineEvent event = SERIALIZER.decode(message.payload());
+
+ DeviceId deviceId = event.deviceId();
+ Timestamp timestamp = event.timestamp();
+
+ try {
+ notifyDelegateIfNotNull(markOfflineInternal(deviceId, timestamp));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling device offline", e);
+ }
+ }
+ }
+
+ private final class InternalRemoveRequestListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received device remove request from peer: {}", message.sender());
+ DeviceId did = SERIALIZER.decode(message.payload());
+
+ try {
+ removeDevice(did);
+ } catch (Exception e) {
+ log.warn("Exception thrown handling device remove", e);
+ }
+ }
+ }
+
+ private final class InternalDeviceRemovedEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received device removed event from peer: {}", message.sender());
+ InternalDeviceRemovedEvent event = SERIALIZER.decode(message.payload());
+
+ DeviceId deviceId = event.deviceId();
+ Timestamp timestamp = event.timestamp();
+
+ try {
+ notifyDelegateIfNotNull(removeDeviceInternal(deviceId, timestamp));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling device removed", e);
+ }
+ }
+ }
+
+ private final class InternalPortEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+
+ log.debug("Received port update event from peer: {}", message.sender());
+ InternalPortEvent event = SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = event.providerId();
+ DeviceId deviceId = event.deviceId();
+ Timestamped<List<PortDescription>> portDescriptions = event.portDescriptions();
+
+ if (getDevice(deviceId) == null) {
+ log.debug("{} not found on this node yet, ignoring.", deviceId);
+ // Note: dropped information will be recovered by anti-entropy
+ return;
+ }
+
+ try {
+ notifyDelegate(updatePortsInternal(providerId, deviceId, portDescriptions));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling port update", e);
+ }
+ }
+ }
+
+ private final class InternalPortStatusEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+
+ log.debug("Received port status update event from peer: {}", message.sender());
+ InternalPortStatusEvent event = SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = event.providerId();
+ DeviceId deviceId = event.deviceId();
+ Timestamped<PortDescription> portDescription = event.portDescription();
+
+ if (getDevice(deviceId) == null) {
+ log.debug("{} not found on this node yet, ignoring.", deviceId);
+ // Note: dropped information will be recovered by anti-entropy
+ return;
+ }
+
+ try {
+ notifyDelegateIfNotNull(updatePortStatusInternal(providerId, deviceId, portDescription));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling port update", e);
+ }
+ }
+ }
+
+ private final class InternalDeviceAdvertisementListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.trace("Received Device Anti-Entropy advertisement from peer: {}", message.sender());
+ DeviceAntiEntropyAdvertisement advertisement = SERIALIZER.decode(message.payload());
+ try {
+ handleAdvertisement(advertisement);
+ } catch (Exception e) {
+ log.warn("Exception thrown handling Device advertisements.", e);
+ }
+ }
+ }
+
+ private final class DeviceInjectedEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received injected device event from peer: {}", message.sender());
+ DeviceInjectedEvent event = SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = event.providerId();
+ DeviceId deviceId = event.deviceId();
+ DeviceDescription deviceDescription = event.deviceDescription();
+ if (!deviceClockService.isTimestampAvailable(deviceId)) {
+ // workaround for ONOS-1208
+ log.warn("Not ready to accept update. Dropping {}", deviceDescription);
+ return;
+ }
+
+ try {
+ createOrUpdateDevice(providerId, deviceId, deviceDescription);
+ } catch (Exception e) {
+ log.warn("Exception thrown handling device injected event.", e);
+ }
+ }
+ }
+
+ private final class PortInjectedEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received injected port event from peer: {}", message.sender());
+ PortInjectedEvent event = SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = event.providerId();
+ DeviceId deviceId = event.deviceId();
+ List<PortDescription> portDescriptions = event.portDescriptions();
+ if (!deviceClockService.isTimestampAvailable(deviceId)) {
+ // workaround for ONOS-1208
+ log.warn("Not ready to accept update. Dropping {}", portDescriptions);
+ return;
+ }
+
+ try {
+ updatePorts(providerId, deviceId, portDescriptions);
+ } catch (Exception e) {
+ log.warn("Exception thrown handling port injected event.", e);
+ }
+ }
+ }
+
+ private class InternalPortStatsListener
+ implements EventuallyConsistentMapListener<DeviceId, Map<PortNumber, PortStatistics>> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<DeviceId, Map<PortNumber, PortStatistics>> event) {
+ if (event.type() == PUT) {
+ Device device = devices.get(event.key());
+ if (device != null) {
+ delegate.notify(new DeviceEvent(PORT_STATS_UPDATED, device));
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStoreMessageSubjects.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStoreMessageSubjects.java
new file mode 100644
index 00000000..554faf91
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStoreMessageSubjects.java
@@ -0,0 +1,41 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by GossipDeviceStore peer-peer communication.
+ */
+public final class GossipDeviceStoreMessageSubjects {
+
+ private GossipDeviceStoreMessageSubjects() {}
+
+ public static final MessageSubject DEVICE_UPDATE = new MessageSubject("peer-device-update");
+ public static final MessageSubject DEVICE_OFFLINE = new MessageSubject("peer-device-offline");
+ public static final MessageSubject DEVICE_REMOVE_REQ = new MessageSubject("peer-device-remove-request");
+ public static final MessageSubject DEVICE_REMOVED = new MessageSubject("peer-device-removed");
+ public static final MessageSubject PORT_UPDATE = new MessageSubject("peer-port-update");
+ public static final MessageSubject PORT_STATUS_UPDATE = new MessageSubject("peer-port-status-update");
+
+ public static final MessageSubject DEVICE_ADVERTISE = new MessageSubject("peer-device-advertisements");
+ // to be used with 3-way anti-entropy process
+ public static final MessageSubject DEVICE_REQUEST = new MessageSubject("peer-device-request");
+
+ // Network elements injected (not discovered) by ConfigProvider
+ public static final MessageSubject DEVICE_INJECTED = new MessageSubject("peer-device-injected");
+ public static final MessageSubject PORT_INJECTED = new MessageSubject("peer-port-injected");
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEvent.java
new file mode 100644
index 00000000..6916a3ed
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEvent.java
@@ -0,0 +1,71 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * change event.
+ */
+public class InternalDeviceEvent {
+
+ private final ProviderId providerId;
+ private final DeviceId deviceId;
+ private final Timestamped<DeviceDescription> deviceDescription;
+
+ protected InternalDeviceEvent(
+ ProviderId providerId,
+ DeviceId deviceId,
+ Timestamped<DeviceDescription> deviceDescription) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.deviceDescription = deviceDescription;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public Timestamped<DeviceDescription> deviceDescription() {
+ return deviceDescription;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("deviceDescription", deviceDescription)
+ .toString();
+ }
+
+ // for serializer
+ protected InternalDeviceEvent() {
+ this.providerId = null;
+ this.deviceId = null;
+ this.deviceDescription = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEventSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEventSerializer.java
new file mode 100644
index 00000000..d5fbde7e
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceEventSerializer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link InternalDeviceEvent}.
+ */
+public class InternalDeviceEventSerializer extends Serializer<InternalDeviceEvent> {
+
+ /**
+ * Creates a serializer for {@link InternalDeviceEvent}.
+ */
+ public InternalDeviceEventSerializer() {
+ // does not accept null
+ super(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, InternalDeviceEvent event) {
+ kryo.writeClassAndObject(output, event.providerId());
+ kryo.writeClassAndObject(output, event.deviceId());
+ kryo.writeClassAndObject(output, event.deviceDescription());
+ }
+
+ @Override
+ public InternalDeviceEvent read(Kryo kryo, Input input,
+ Class<InternalDeviceEvent> type) {
+ ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+ DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+
+ @SuppressWarnings("unchecked")
+ Timestamped<DeviceDescription> deviceDescription
+ = (Timestamped<DeviceDescription>) kryo.readClassAndObject(input);
+
+ return new InternalDeviceEvent(providerId, deviceId, deviceDescription);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEvent.java
new file mode 100644
index 00000000..0546c139
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEvent.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * going offline.
+ */
+public class InternalDeviceOfflineEvent {
+
+ private final DeviceId deviceId;
+ private final Timestamp timestamp;
+
+ /**
+ * Creates a InternalDeviceOfflineEvent.
+ * @param deviceId identifier of device going offline.
+ * @param timestamp timestamp of when the device went offline.
+ */
+ public InternalDeviceOfflineEvent(DeviceId deviceId, Timestamp timestamp) {
+ this.deviceId = deviceId;
+ this.timestamp = timestamp;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public Timestamp timestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("deviceId", deviceId)
+ .add("timestamp", timestamp)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private InternalDeviceOfflineEvent() {
+ deviceId = null;
+ timestamp = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEventSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEventSerializer.java
new file mode 100644
index 00000000..7f3c7bcf
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceOfflineEventSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Timestamp;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link InternalDeviceOfflineEvent}.
+ */
+public class InternalDeviceOfflineEventSerializer extends Serializer<InternalDeviceOfflineEvent> {
+
+ /**
+ * Creates a serializer for {@link InternalDeviceOfflineEvent}.
+ */
+ public InternalDeviceOfflineEventSerializer() {
+ // does not accept null
+ super(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, InternalDeviceOfflineEvent event) {
+ kryo.writeClassAndObject(output, event.deviceId());
+ kryo.writeClassAndObject(output, event.timestamp());
+ }
+
+ @Override
+ public InternalDeviceOfflineEvent read(Kryo kryo, Input input,
+ Class<InternalDeviceOfflineEvent> type) {
+ DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+ Timestamp timestamp = (Timestamp) kryo.readClassAndObject(input);
+
+ return new InternalDeviceOfflineEvent(deviceId, timestamp);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceRemovedEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceRemovedEvent.java
new file mode 100644
index 00000000..e9f4f06a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalDeviceRemovedEvent.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * being administratively removed.
+ */
+public class InternalDeviceRemovedEvent {
+
+ private final DeviceId deviceId;
+ private final Timestamp timestamp;
+
+ /**
+ * Creates a InternalDeviceRemovedEvent.
+ * @param deviceId identifier of the removed device.
+ * @param timestamp timestamp of when the device was administratively removed.
+ */
+ public InternalDeviceRemovedEvent(DeviceId deviceId, Timestamp timestamp) {
+ this.deviceId = deviceId;
+ this.timestamp = timestamp;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public Timestamp timestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("deviceId", deviceId)
+ .add("timestamp", timestamp)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private InternalDeviceRemovedEvent() {
+ deviceId = null;
+ timestamp = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEvent.java
new file mode 100644
index 00000000..f92fb115
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEvent.java
@@ -0,0 +1,73 @@
+/*
+ * 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.store.device.impl;
+
+import java.util.List;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a port
+ * change event.
+ */
+public class InternalPortEvent {
+
+ private final ProviderId providerId;
+ private final DeviceId deviceId;
+ private final Timestamped<List<PortDescription>> portDescriptions;
+
+ protected InternalPortEvent(
+ ProviderId providerId,
+ DeviceId deviceId,
+ Timestamped<List<PortDescription>> portDescriptions) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.portDescriptions = portDescriptions;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public Timestamped<List<PortDescription>> portDescriptions() {
+ return portDescriptions;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("portDescriptions", portDescriptions)
+ .toString();
+ }
+
+ // for serializer
+ protected InternalPortEvent() {
+ this.providerId = null;
+ this.deviceId = null;
+ this.portDescriptions = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEventSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEventSerializer.java
new file mode 100644
index 00000000..0acd703f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortEventSerializer.java
@@ -0,0 +1,62 @@
+/*
+ * 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.store.device.impl;
+
+import java.util.List;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link InternalPortEvent}.
+ */
+public class InternalPortEventSerializer extends Serializer<InternalPortEvent> {
+
+ /**
+ * Creates a serializer for {@link InternalPortEvent}.
+ */
+ public InternalPortEventSerializer() {
+ // does not accept null
+ super(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, InternalPortEvent event) {
+ kryo.writeClassAndObject(output, event.providerId());
+ kryo.writeClassAndObject(output, event.deviceId());
+ kryo.writeClassAndObject(output, event.portDescriptions());
+ }
+
+ @Override
+ public InternalPortEvent read(Kryo kryo, Input input,
+ Class<InternalPortEvent> type) {
+ ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+ DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+
+ @SuppressWarnings("unchecked")
+ Timestamped<List<PortDescription>> portDescriptions
+ = (Timestamped<List<PortDescription>>) kryo.readClassAndObject(input);
+
+ return new InternalPortEvent(providerId, deviceId, portDescriptions);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEvent.java
new file mode 100644
index 00000000..f1781693
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEvent.java
@@ -0,0 +1,71 @@
+/*
+ * 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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a port
+ * status change event.
+ */
+public class InternalPortStatusEvent {
+
+ private final ProviderId providerId;
+ private final DeviceId deviceId;
+ private final Timestamped<PortDescription> portDescription;
+
+ protected InternalPortStatusEvent(
+ ProviderId providerId,
+ DeviceId deviceId,
+ Timestamped<PortDescription> portDescription) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.portDescription = portDescription;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public Timestamped<PortDescription> portDescription() {
+ return portDescription;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("portDescription", portDescription)
+ .toString();
+ }
+
+ // for serializer
+ protected InternalPortStatusEvent() {
+ this.providerId = null;
+ this.deviceId = null;
+ this.portDescription = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEventSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEventSerializer.java
new file mode 100644
index 00000000..32ee3915
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/InternalPortStatusEventSerializer.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.store.device.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link InternalPortStatusEvent}.
+ */
+public class InternalPortStatusEventSerializer extends Serializer<InternalPortStatusEvent> {
+
+ /**
+ * Creates a serializer for {@link InternalPortStatusEvent}.
+ */
+ public InternalPortStatusEventSerializer() {
+ // does not accept null
+ super(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, InternalPortStatusEvent event) {
+ kryo.writeClassAndObject(output, event.providerId());
+ kryo.writeClassAndObject(output, event.deviceId());
+ kryo.writeClassAndObject(output, event.portDescription());
+ }
+
+ @Override
+ public InternalPortStatusEvent read(Kryo kryo, Input input,
+ Class<InternalPortStatusEvent> type) {
+ ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+ DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+ @SuppressWarnings("unchecked")
+ Timestamped<PortDescription> portDescription = (Timestamped<PortDescription>) kryo.readClassAndObject(input);
+
+ return new InternalPortStatusEvent(providerId, deviceId, portDescription);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortFragmentId.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortFragmentId.java
new file mode 100644
index 00000000..ed0ccaa1
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortFragmentId.java
@@ -0,0 +1,76 @@
+/*
+ * 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.store.device.impl;
+
+import java.util.Objects;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Identifier for PortDescription from a Provider.
+ */
+public final class PortFragmentId {
+ public final ProviderId providerId;
+ public final DeviceId deviceId;
+ public final PortNumber portNumber;
+
+ public PortFragmentId(DeviceId deviceId, ProviderId providerId,
+ PortNumber portNumber) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.portNumber = portNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerId, deviceId, portNumber);
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PortFragmentId)) {
+ return false;
+ }
+ PortFragmentId that = (PortFragmentId) obj;
+ return Objects.equals(this.deviceId, that.deviceId) &&
+ Objects.equals(this.portNumber, that.portNumber) &&
+ Objects.equals(this.providerId, that.providerId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("portNumber", portNumber)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private PortFragmentId() {
+ this.providerId = null;
+ this.deviceId = null;
+ this.portNumber = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortInjectedEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortInjectedEvent.java
new file mode 100644
index 00000000..c80f8105
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortInjectedEvent.java
@@ -0,0 +1,50 @@
+package org.onosproject.store.device.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.List;
+
+public class PortInjectedEvent {
+
+ private ProviderId providerId;
+ private DeviceId deviceId;
+ private List<PortDescription> portDescriptions;
+
+ protected PortInjectedEvent(ProviderId providerId, DeviceId deviceId, List<PortDescription> portDescriptions) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.portDescriptions = portDescriptions;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public List<PortDescription> portDescriptions() {
+ return portDescriptions;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("portDescriptions", portDescriptions)
+ .toString();
+ }
+
+ // for serializer
+ protected PortInjectedEvent() {
+ this.providerId = null;
+ this.deviceId = null;
+ this.portDescriptions = null;
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortKey.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortKey.java
new file mode 100644
index 00000000..62b09952
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/PortKey.java
@@ -0,0 +1,79 @@
+/*
+ * 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.store.device.impl;
+
+import java.util.Objects;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Key for PortDescriptions in ECDeviceStore.
+ */
+public class PortKey {
+ private final ProviderId providerId;
+ private final DeviceId deviceId;
+ private final PortNumber portNumber;
+
+ public PortKey(ProviderId providerId, DeviceId deviceId, PortNumber portNumber) {
+ this.providerId = providerId;
+ this.deviceId = deviceId;
+ this.portNumber = portNumber;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public PortNumber portNumber() {
+ return portNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerId, deviceId, portNumber);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PortKey)) {
+ return false;
+ }
+ PortKey that = (PortKey) obj;
+ return Objects.equals(this.deviceId, that.deviceId) &&
+ Objects.equals(this.providerId, that.providerId) &&
+ Objects.equals(this.portNumber, that.portNumber);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("deviceId", deviceId)
+ .add("portNumber", portNumber)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/package-info.java
new file mode 100644
index 00000000..29df62ec
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of distributed device store using p2p synchronization protocol.
+ */
+package org.onosproject.store.device.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/AntiEntropyAdvertisement.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/AntiEntropyAdvertisement.java
new file mode 100644
index 00000000..d783fe22
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/AntiEntropyAdvertisement.java
@@ -0,0 +1,71 @@
+/*
+ * 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.store.ecmap;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableMap;
+
+import org.onosproject.cluster.NodeId;
+
+import java.util.Map;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Anti-entropy advertisement message for eventually consistent map.
+ */
+public class AntiEntropyAdvertisement<K> {
+
+ private final NodeId sender;
+ private final Map<K, MapValue.Digest> digest;
+
+ /**
+ * Creates a new anti entropy advertisement message.
+ *
+ * @param sender the sender's node ID
+ * @param digest for map entries
+ */
+ public AntiEntropyAdvertisement(NodeId sender,
+ Map<K, MapValue.Digest> digest) {
+ this.sender = checkNotNull(sender);
+ this.digest = ImmutableMap.copyOf(checkNotNull(digest));
+ }
+
+ /**
+ * Returns the sender's node ID.
+ *
+ * @return the sender's node ID
+ */
+ public NodeId sender() {
+ return sender;
+ }
+
+ /**
+ * Returns the digest for map entries.
+ *
+ * @return mapping from key to associated digest
+ */
+ public Map<K, MapValue.Digest> digest() {
+ return digest;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("sender", sender)
+ .add("totalEntries", digest.size())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapBuilderImpl.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapBuilderImpl.java
new file mode 100644
index 00000000..a553ffff
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapBuilderImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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.store.ecmap;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapBuilder;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Eventually consistent map builder.
+ */
+public class EventuallyConsistentMapBuilderImpl<K, V>
+ implements EventuallyConsistentMapBuilder<K, V> {
+ private final ClusterService clusterService;
+ private final ClusterCommunicationService clusterCommunicator;
+
+ private String name;
+ private KryoNamespace.Builder serializerBuilder;
+ private ExecutorService eventExecutor;
+ private ExecutorService communicationExecutor;
+ private ScheduledExecutorService backgroundExecutor;
+ private BiFunction<K, V, Timestamp> timestampProvider;
+ private BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
+ private boolean tombstonesDisabled = false;
+ private long antiEntropyPeriod = 5;
+ private TimeUnit antiEntropyTimeUnit = TimeUnit.SECONDS;
+ private boolean convergeFaster = false;
+ private boolean persistent = false;
+
+ /**
+ * Creates a new eventually consistent map builder.
+ *
+ * @param clusterService cluster service
+ * @param clusterCommunicator cluster communication service
+ */
+ public EventuallyConsistentMapBuilderImpl(ClusterService clusterService,
+ ClusterCommunicationService clusterCommunicator) {
+ this.clusterService = checkNotNull(clusterService);
+ this.clusterCommunicator = checkNotNull(clusterCommunicator);
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withName(String name) {
+ this.name = checkNotNull(name);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withSerializer(
+ KryoNamespace.Builder serializerBuilder) {
+ this.serializerBuilder = checkNotNull(serializerBuilder);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withTimestampProvider(
+ BiFunction<K, V, Timestamp> timestampProvider) {
+ this.timestampProvider = checkNotNull(timestampProvider);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withEventExecutor(ExecutorService executor) {
+ this.eventExecutor = checkNotNull(executor);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withCommunicationExecutor(
+ ExecutorService executor) {
+ communicationExecutor = checkNotNull(executor);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withBackgroundExecutor(ScheduledExecutorService executor) {
+ this.backgroundExecutor = checkNotNull(executor);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withPeerUpdateFunction(
+ BiFunction<K, V, Collection<NodeId>> peerUpdateFunction) {
+ this.peerUpdateFunction = checkNotNull(peerUpdateFunction);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withTombstonesDisabled() {
+ tombstonesDisabled = true;
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withAntiEntropyPeriod(long period, TimeUnit unit) {
+ checkArgument(period > 0, "anti-entropy period must be greater than 0");
+ antiEntropyPeriod = period;
+ antiEntropyTimeUnit = checkNotNull(unit);
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withFasterConvergence() {
+ convergeFaster = true;
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMapBuilder<K, V> withPersistence() {
+ persistent = true;
+ return this;
+ }
+
+ @Override
+ public EventuallyConsistentMap<K, V> build() {
+ checkNotNull(name, "name is a mandatory parameter");
+ checkNotNull(serializerBuilder, "serializerBuilder is a mandatory parameter");
+ checkNotNull(timestampProvider, "timestampProvider is a mandatory parameter");
+
+ return new EventuallyConsistentMapImpl<>(name,
+ clusterService,
+ clusterCommunicator,
+ serializerBuilder,
+ timestampProvider,
+ peerUpdateFunction,
+ eventExecutor,
+ communicationExecutor,
+ backgroundExecutor,
+ tombstonesDisabled,
+ antiEntropyPeriod,
+ antiEntropyTimeUnit,
+ convergeFaster,
+ persistent);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java
new file mode 100644
index 00000000..2859b62f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java
@@ -0,0 +1,678 @@
+/*
+ * 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.store.ecmap;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.util.AbstractAccumulator;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.SlidingWindowCounter;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.impl.LogicalTimestamp;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.Timer;
+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 java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Distributed Map implementation which uses optimistic replication and gossip
+ * based techniques to provide an eventually consistent data store.
+ */
+public class EventuallyConsistentMapImpl<K, V>
+ implements EventuallyConsistentMap<K, V> {
+
+ private static final Logger log = LoggerFactory.getLogger(EventuallyConsistentMapImpl.class);
+
+ private final Map<K, MapValue<V>> items;
+
+ private final ClusterService clusterService;
+ private final ClusterCommunicationService clusterCommunicator;
+ private final KryoSerializer serializer;
+ private final NodeId localNodeId;
+
+ private final BiFunction<K, V, Timestamp> timestampProvider;
+
+ private final MessageSubject updateMessageSubject;
+ private final MessageSubject antiEntropyAdvertisementSubject;
+
+ private final Set<EventuallyConsistentMapListener<K, V>> listeners
+ = Sets.newCopyOnWriteArraySet();
+
+ private final ExecutorService executor;
+ private final ScheduledExecutorService backgroundExecutor;
+ private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
+
+ private final ExecutorService communicationExecutor;
+ private final Map<NodeId, EventAccumulator> senderPending;
+
+ private final String mapName;
+
+ private volatile boolean destroyed = false;
+ private static final String ERROR_DESTROYED = " map is already destroyed";
+ private final String destroyedMessage;
+
+ private static final String ERROR_NULL_KEY = "Key cannot be null";
+ private static final String ERROR_NULL_VALUE = "Null values are not allowed";
+
+ private final long initialDelaySec = 5;
+ private final boolean lightweightAntiEntropy;
+ private final boolean tombstonesDisabled;
+
+ private static final int WINDOW_SIZE = 5;
+ private static final int HIGH_LOAD_THRESHOLD = 0;
+ private static final int LOAD_WINDOW = 2;
+ private SlidingWindowCounter counter = new SlidingWindowCounter(WINDOW_SIZE);
+
+ private final boolean persistent;
+ private final PersistentStore<K, V> persistentStore;
+
+ /**
+ * Creates a new eventually consistent map shared amongst multiple instances.
+ * <p>
+ * See {@link org.onosproject.store.service.EventuallyConsistentMapBuilder}
+ * for more description of the parameters expected by the map.
+ * </p>
+ *
+ * @param mapName a String identifier for the map.
+ * @param clusterService the cluster service
+ * @param clusterCommunicator the cluster communications service
+ * @param serializerBuilder a Kryo namespace builder that can serialize
+ * both K and V
+ * @param timestampProvider provider of timestamps for K and V
+ * @param peerUpdateFunction function that provides a set of nodes to immediately
+ * update to when there writes to the map
+ * @param eventExecutor executor to use for processing incoming
+ * events from peers
+ * @param communicationExecutor executor to use for sending events to peers
+ * @param backgroundExecutor executor to use for background anti-entropy
+ * tasks
+ * @param tombstonesDisabled true if this map should not maintain
+ * tombstones
+ * @param antiEntropyPeriod period that the anti-entropy task should run
+ * @param antiEntropyTimeUnit time unit for anti-entropy period
+ * @param convergeFaster make anti-entropy try to converge faster
+ * @param persistent persist data to disk
+ */
+ EventuallyConsistentMapImpl(String mapName,
+ ClusterService clusterService,
+ ClusterCommunicationService clusterCommunicator,
+ KryoNamespace.Builder serializerBuilder,
+ BiFunction<K, V, Timestamp> timestampProvider,
+ BiFunction<K, V, Collection<NodeId>> peerUpdateFunction,
+ ExecutorService eventExecutor,
+ ExecutorService communicationExecutor,
+ ScheduledExecutorService backgroundExecutor,
+ boolean tombstonesDisabled,
+ long antiEntropyPeriod,
+ TimeUnit antiEntropyTimeUnit,
+ boolean convergeFaster,
+ boolean persistent) {
+ this.mapName = mapName;
+ items = Maps.newConcurrentMap();
+ senderPending = Maps.newConcurrentMap();
+ destroyedMessage = mapName + ERROR_DESTROYED;
+
+ this.clusterService = clusterService;
+ this.clusterCommunicator = clusterCommunicator;
+ this.localNodeId = clusterService.getLocalNode().id();
+
+ this.serializer = createSerializer(serializerBuilder);
+
+ this.timestampProvider = timestampProvider;
+
+ if (peerUpdateFunction != null) {
+ this.peerUpdateFunction = peerUpdateFunction;
+ } else {
+ this.peerUpdateFunction = (key, value) -> clusterService.getNodes().stream()
+ .map(ControllerNode::id)
+ .filter(nodeId -> !nodeId.equals(localNodeId))
+ .collect(Collectors.toList());
+ }
+
+ if (eventExecutor != null) {
+ this.executor = eventExecutor;
+ } else {
+ // should be a normal executor; it's used for receiving messages
+ this.executor =
+ Executors.newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-fg-%d"));
+ }
+
+ if (communicationExecutor != null) {
+ this.communicationExecutor = communicationExecutor;
+ } else {
+ // sending executor; should be capped
+ //TODO this probably doesn't need to be bounded anymore
+ this.communicationExecutor =
+ newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-publish-%d"));
+ }
+
+ this.persistent = persistent;
+
+ if (this.persistent) {
+ String dataDirectory = System.getProperty("karaf.data", "./data");
+ String filename = dataDirectory + "/" + "mapdb-ecm-" + mapName;
+
+ ExecutorService dbExecutor =
+ newFixedThreadPool(1, groupedThreads("onos/ecm", mapName + "-dbwriter"));
+
+ persistentStore = new MapDbPersistentStore<>(filename, dbExecutor, serializer);
+ persistentStore.readInto(items);
+ } else {
+ this.persistentStore = null;
+ }
+
+ if (backgroundExecutor != null) {
+ this.backgroundExecutor = backgroundExecutor;
+ } else {
+ this.backgroundExecutor =
+ newSingleThreadScheduledExecutor(groupedThreads("onos/ecm", mapName + "-bg-%d"));
+ }
+
+ // start anti-entropy thread
+ this.backgroundExecutor.scheduleAtFixedRate(this::sendAdvertisement,
+ initialDelaySec, antiEntropyPeriod,
+ antiEntropyTimeUnit);
+
+ updateMessageSubject = new MessageSubject("ecm-" + mapName + "-update");
+ clusterCommunicator.addSubscriber(updateMessageSubject,
+ serializer::decode,
+ this::processUpdates,
+ this.executor);
+
+ antiEntropyAdvertisementSubject = new MessageSubject("ecm-" + mapName + "-anti-entropy");
+ clusterCommunicator.addSubscriber(antiEntropyAdvertisementSubject,
+ serializer::decode,
+ this::handleAntiEntropyAdvertisement,
+ this.backgroundExecutor);
+
+ this.tombstonesDisabled = tombstonesDisabled;
+ this.lightweightAntiEntropy = !convergeFaster;
+ }
+
+ private KryoSerializer createSerializer(KryoNamespace.Builder builder) {
+ return new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ // Add the map's internal helper classes to the user-supplied serializer
+ serializerPool = builder
+ .register(KryoNamespaces.BASIC)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .register(LogicalTimestamp.class)
+ .register(WallClockTimestamp.class)
+ .register(AntiEntropyAdvertisement.class)
+ .register(UpdateEntry.class)
+ .register(MapValue.class)
+ .register(MapValue.Digest.class)
+ .build();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ checkState(!destroyed, destroyedMessage);
+ // TODO: Maintain a separate counter for tracking live elements in map.
+ return Maps.filterValues(items, MapValue::isAlive).size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ checkState(!destroyed, destroyedMessage);
+ return size() == 0;
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ return get(key) != null;
+ }
+
+ @Override
+ public boolean containsValue(V value) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ return items.values()
+ .stream()
+ .filter(MapValue::isAlive)
+ .anyMatch(v -> value.equals(v.get()));
+ }
+
+ @Override
+ public V get(K key) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+
+ MapValue<V> value = items.get(key);
+ return (value == null || value.isTombstone()) ? null : value.get();
+ }
+
+ @Override
+ public void put(K key, V value) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+
+ MapValue<V> newValue = new MapValue<>(value, timestampProvider.apply(key, value));
+ if (putInternal(key, newValue)) {
+ notifyPeers(new UpdateEntry<>(key, newValue), peerUpdateFunction.apply(key, value));
+ notifyListeners(new EventuallyConsistentMapEvent<>(mapName, PUT, key, value));
+ }
+ }
+
+ @Override
+ public V remove(K key) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ return removeAndNotify(key, null);
+ }
+
+ @Override
+ public void remove(K key, V value) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ removeAndNotify(key, value);
+ }
+
+ private V removeAndNotify(K key, V value) {
+ Timestamp timestamp = timestampProvider.apply(key, value);
+ Optional<MapValue<V>> tombstone = tombstonesDisabled || timestamp == null
+ ? Optional.empty() : Optional.of(MapValue.tombstone(timestamp));
+ MapValue<V> previousValue = removeInternal(key, Optional.ofNullable(value), tombstone);
+ if (previousValue != null) {
+ notifyPeers(new UpdateEntry<>(key, tombstone.orElse(null)),
+ peerUpdateFunction.apply(key, previousValue.get()));
+ if (previousValue.isAlive()) {
+ notifyListeners(new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, previousValue.get()));
+ }
+ }
+ return previousValue != null ? previousValue.get() : null;
+ }
+
+ private MapValue<V> removeInternal(K key, Optional<V> value, Optional<MapValue<V>> tombstone) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(value, ERROR_NULL_VALUE);
+ tombstone.ifPresent(v -> checkState(v.isTombstone()));
+
+ counter.incrementCount();
+ AtomicBoolean updated = new AtomicBoolean(false);
+ AtomicReference<MapValue<V>> previousValue = new AtomicReference<>();
+ items.compute(key, (k, existing) -> {
+ boolean valueMatches = true;
+ if (value.isPresent() && existing != null && existing.isAlive()) {
+ valueMatches = Objects.equals(value.get(), existing.get());
+ }
+ if (existing == null) {
+ log.debug("ECMap Remove: Existing value for key {} is already null", k);
+ }
+ if (valueMatches) {
+ if (existing == null) {
+ updated.set(tombstone.isPresent());
+ } else {
+ updated.set(!tombstone.isPresent() || tombstone.get().isNewerThan(existing));
+ }
+ }
+ if (updated.get()) {
+ previousValue.set(existing);
+ return tombstone.orElse(null);
+ } else {
+ return existing;
+ }
+ });
+ if (updated.get()) {
+ if (persistent) {
+ if (tombstone.isPresent()) {
+ persistentStore.update(key, tombstone.get());
+ } else {
+ persistentStore.remove(key);
+ }
+ }
+ }
+ return previousValue.get();
+ }
+
+ @Override
+ public V compute(K key, BiFunction<K, V, V> recomputeFunction) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(recomputeFunction, "Recompute function cannot be null");
+
+ AtomicBoolean updated = new AtomicBoolean(false);
+ AtomicReference<MapValue<V>> previousValue = new AtomicReference<>();
+ MapValue<V> computedValue = items.compute(key, (k, mv) -> {
+ previousValue.set(mv);
+ V newRawValue = recomputeFunction.apply(key, mv == null ? null : mv.get());
+ MapValue<V> newValue = new MapValue<>(newRawValue, timestampProvider.apply(key, newRawValue));
+ if (mv == null || newValue.isNewerThan(mv)) {
+ updated.set(true);
+ return newValue;
+ } else {
+ return mv;
+ }
+ });
+ if (updated.get()) {
+ notifyPeers(new UpdateEntry<>(key, computedValue), peerUpdateFunction.apply(key, computedValue.get()));
+ EventuallyConsistentMapEvent.Type updateType = computedValue.isTombstone() ? REMOVE : PUT;
+ V value = computedValue.isTombstone()
+ ? previousValue.get() == null ? null : previousValue.get().get()
+ : computedValue.get();
+ if (value != null) {
+ notifyListeners(new EventuallyConsistentMapEvent<>(mapName, updateType, key, value));
+ }
+ }
+ return computedValue.get();
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ checkState(!destroyed, destroyedMessage);
+ m.forEach(this::put);
+ }
+
+ @Override
+ public void clear() {
+ checkState(!destroyed, destroyedMessage);
+ Maps.filterValues(items, MapValue::isAlive)
+ .forEach((k, v) -> remove(k));
+ }
+
+ @Override
+ public Set<K> keySet() {
+ checkState(!destroyed, destroyedMessage);
+ return Maps.filterValues(items, MapValue::isAlive)
+ .keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ checkState(!destroyed, destroyedMessage);
+ return Collections2.transform(Maps.filterValues(items, MapValue::isAlive).values(), MapValue::get);
+ }
+
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ checkState(!destroyed, destroyedMessage);
+ return Maps.filterValues(items, MapValue::isAlive)
+ .entrySet()
+ .stream()
+ .map(e -> Pair.of(e.getKey(), e.getValue().get()))
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns true if newValue was accepted i.e. map is updated.
+ * @param key key
+ * @param newValue proposed new value
+ * @return true if update happened; false if map already contains a more recent value for the key
+ */
+ private boolean putInternal(K key, MapValue<V> newValue) {
+ checkState(!destroyed, destroyedMessage);
+ checkNotNull(key, ERROR_NULL_KEY);
+ checkNotNull(newValue, ERROR_NULL_VALUE);
+ checkState(newValue.isAlive());
+ counter.incrementCount();
+ AtomicBoolean updated = new AtomicBoolean(false);
+ items.compute(key, (k, existing) -> {
+ if (existing == null || newValue.isNewerThan(existing)) {
+ updated.set(true);
+ return newValue;
+ }
+ return existing;
+ });
+ if (updated.get() && persistent) {
+ persistentStore.update(key, newValue);
+ }
+ return updated.get();
+ }
+
+ @Override
+ public void addListener(EventuallyConsistentMapListener<K, V> listener) {
+ checkState(!destroyed, destroyedMessage);
+
+ listeners.add(checkNotNull(listener));
+ }
+
+ @Override
+ public void removeListener(EventuallyConsistentMapListener<K, V> listener) {
+ checkState(!destroyed, destroyedMessage);
+
+ listeners.remove(checkNotNull(listener));
+ }
+
+ @Override
+ public void destroy() {
+ destroyed = true;
+
+ executor.shutdown();
+ backgroundExecutor.shutdown();
+ communicationExecutor.shutdown();
+
+ listeners.clear();
+
+ clusterCommunicator.removeSubscriber(updateMessageSubject);
+ clusterCommunicator.removeSubscriber(antiEntropyAdvertisementSubject);
+ }
+
+ private void notifyListeners(EventuallyConsistentMapEvent<K, V> event) {
+ listeners.forEach(listener -> listener.event(event));
+ }
+
+ private void notifyPeers(UpdateEntry<K, V> event, Collection<NodeId> peers) {
+ queueUpdate(event, peers);
+ }
+
+ private void queueUpdate(UpdateEntry<K, V> event, Collection<NodeId> peers) {
+ if (peers == null) {
+ // we have no friends :(
+ return;
+ }
+ peers.forEach(node ->
+ senderPending.computeIfAbsent(node, unusedKey -> new EventAccumulator(node)).add(event)
+ );
+ }
+
+ private boolean underHighLoad() {
+ return counter.get(LOAD_WINDOW) > HIGH_LOAD_THRESHOLD;
+ }
+
+ private void sendAdvertisement() {
+ try {
+ if (underHighLoad() || destroyed) {
+ return;
+ }
+ pickRandomActivePeer().ifPresent(this::sendAdvertisementToPeer);
+ } catch (Exception e) {
+ // Catch all exceptions to avoid scheduled task being suppressed.
+ log.error("Exception thrown while sending advertisement", e);
+ }
+ }
+
+ private Optional<NodeId> pickRandomActivePeer() {
+ List<NodeId> activePeers = clusterService.getNodes()
+ .stream()
+ .map(ControllerNode::id)
+ .filter(id -> !localNodeId.equals(id))
+ .filter(id -> clusterService.getState(id) == ControllerNode.State.ACTIVE)
+ .collect(Collectors.toList());
+ Collections.shuffle(activePeers);
+ return activePeers.isEmpty() ? Optional.empty() : Optional.of(activePeers.get(0));
+ }
+
+ private void sendAdvertisementToPeer(NodeId peer) {
+ clusterCommunicator.unicast(createAdvertisement(),
+ antiEntropyAdvertisementSubject,
+ serializer::encode,
+ peer)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ log.debug("Failed to send anti-entropy advertisement to {}", peer, error);
+ }
+ });
+ }
+
+ private AntiEntropyAdvertisement<K> createAdvertisement() {
+ return new AntiEntropyAdvertisement<K>(localNodeId,
+ ImmutableMap.copyOf(Maps.transformValues(items, MapValue::digest)));
+ }
+
+ private void handleAntiEntropyAdvertisement(AntiEntropyAdvertisement<K> ad) {
+ if (destroyed || underHighLoad()) {
+ return;
+ }
+ try {
+ log.debug("Received anti-entropy advertisement from {} for {} with {} entries in it",
+ mapName, ad.sender(), ad.digest().size());
+ antiEntropyCheckLocalItems(ad).forEach(this::notifyListeners);
+
+ if (!lightweightAntiEntropy) {
+ // if remote ad has any entries that the local copy is missing, actively sync
+ // TODO: Missing keys is not the way local copy can be behind.
+ if (Sets.difference(ad.digest().keySet(), items.keySet()).size() > 0) {
+ // TODO: Send ad for missing keys and for entries that are stale
+ sendAdvertisementToPeer(ad.sender());
+ }
+ }
+ } catch (Exception e) {
+ log.warn("Error handling anti-entropy advertisement", e);
+ }
+ }
+
+ /**
+ * Processes anti-entropy ad from peer by taking following actions:
+ * 1. If peer has an old entry, updates peer.
+ * 2. If peer indicates an entry is removed and has a more recent
+ * timestamp than the local entry, update local state.
+ */
+ private List<EventuallyConsistentMapEvent<K, V>> antiEntropyCheckLocalItems(
+ AntiEntropyAdvertisement<K> ad) {
+ final List<EventuallyConsistentMapEvent<K, V>> externalEvents = Lists.newLinkedList();
+ final NodeId sender = ad.sender();
+ items.forEach((key, localValue) -> {
+ MapValue.Digest remoteValueDigest = ad.digest().get(key);
+ if (remoteValueDigest == null || localValue.isNewerThan(remoteValueDigest.timestamp())) {
+ // local value is more recent, push to sender
+ queueUpdate(new UpdateEntry<>(key, localValue), ImmutableList.of(sender));
+ }
+ if (remoteValueDigest != null
+ && remoteValueDigest.isNewerThan(localValue.digest())
+ && remoteValueDigest.isTombstone()) {
+ MapValue<V> tombstone = MapValue.tombstone(remoteValueDigest.timestamp());
+ MapValue<V> previousValue = removeInternal(key,
+ Optional.empty(),
+ Optional.of(tombstone));
+ if (previousValue != null && previousValue.isAlive()) {
+ externalEvents.add(new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, previousValue.get()));
+ }
+ }
+ });
+ return externalEvents;
+ }
+
+ private void processUpdates(Collection<UpdateEntry<K, V>> updates) {
+ if (destroyed) {
+ return;
+ }
+ updates.forEach(update -> {
+ final K key = update.key();
+ final MapValue<V> value = update.value();
+ if (value == null || value.isTombstone()) {
+ MapValue<V> previousValue = removeInternal(key, Optional.empty(), Optional.ofNullable(value));
+ if (previousValue != null && previousValue.isAlive()) {
+ notifyListeners(new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, previousValue.get()));
+ }
+ } else if (putInternal(key, value)) {
+ notifyListeners(new EventuallyConsistentMapEvent<>(mapName, PUT, key, value.get()));
+ }
+ });
+ }
+
+ // TODO pull this into the class if this gets pulled out...
+ 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;
+ private static final Timer TIMER = new Timer("onos-ecm-sender-events");
+
+ private final class EventAccumulator extends AbstractAccumulator<UpdateEntry<K, V>> {
+
+ private final NodeId peer;
+
+ private EventAccumulator(NodeId peer) {
+ super(TIMER, DEFAULT_MAX_EVENTS, DEFAULT_MAX_BATCH_MS, DEFAULT_MAX_IDLE_MS);
+ this.peer = peer;
+ }
+
+ @Override
+ public void processItems(List<UpdateEntry<K, V>> items) {
+ Map<K, UpdateEntry<K, V>> map = Maps.newHashMap();
+ items.forEach(item -> map.compute(item.key(), (key, existing) ->
+ item.isNewerThan(existing) ? item : existing));
+ communicationExecutor.submit(() -> {
+ clusterCommunicator.unicast(ImmutableList.copyOf(map.values()),
+ updateMessageSubject,
+ serializer::encode,
+ peer)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ log.debug("Failed to send to {}", peer, error);
+ }
+ });
+ });
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java
new file mode 100644
index 00000000..e62a2d5c
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java
@@ -0,0 +1,103 @@
+/*
+ * 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.store.ecmap;
+
+import org.mapdb.DB;
+import org.mapdb.DBMaker;
+import org.mapdb.Hasher;
+import org.mapdb.Serializer;
+import org.onosproject.store.serializers.KryoSerializer;
+
+import java.io.File;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * MapDB based implementation of a persistent store.
+ */
+class MapDbPersistentStore<K, V> implements PersistentStore<K, V> {
+
+ private final ExecutorService executor;
+ private final KryoSerializer serializer;
+
+ private final DB database;
+
+ private final Map<byte[], byte[]> items;
+
+ /**
+ * Creates a new MapDB based persistent store.
+ *
+ * @param filename filename of the database on disk
+ * @param executor executor to use for tasks that write to the disk
+ * @param serializer serializer for keys and values
+ */
+ MapDbPersistentStore(String filename, ExecutorService executor,
+ KryoSerializer serializer) {
+ this.executor = checkNotNull(executor);
+ this.serializer = checkNotNull(serializer);
+
+ File databaseFile = new File(filename);
+
+ database = DBMaker.newFileDB(databaseFile).make();
+
+ items = database.createHashMap("items")
+ .keySerializer(Serializer.BYTE_ARRAY)
+ .valueSerializer(Serializer.BYTE_ARRAY)
+ .hasher(Hasher.BYTE_ARRAY)
+ .makeOrGet();
+ }
+
+ @Override
+ public void readInto(Map<K, MapValue<V>> items) {
+ this.items.forEach((keyBytes, valueBytes) ->
+ items.put(serializer.decode(keyBytes),
+ serializer.decode(valueBytes)));
+ }
+
+ @Override
+ public void update(K key, MapValue<V> value) {
+ executor.submit(() -> updateInternal(key, value));
+ }
+
+ @Override
+ public void remove(K key) {
+ executor.submit(() -> removeInternal(key));
+ }
+
+ private void updateInternal(K key, MapValue<V> newValue) {
+ byte[] keyBytes = serializer.encode(key);
+
+ items.compute(keyBytes, (k, existingBytes) -> {
+ MapValue<V> existing = existingBytes == null ? null :
+ serializer.decode(existingBytes);
+ if (existing == null || newValue.isNewerThan(existing)) {
+ return serializer.encode(newValue);
+ } else {
+ return existingBytes;
+ }
+ });
+ database.commit();
+ }
+
+ private void removeInternal(K key) {
+ byte[] keyBytes = serializer.encode(key);
+ items.remove(keyBytes);
+ database.commit();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapValue.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapValue.java
new file mode 100644
index 00000000..bb69b472
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapValue.java
@@ -0,0 +1,158 @@
+/*
+ * 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.store.ecmap;
+
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Representation of a value in EventuallyConsistentMap.
+ *
+ * @param <V> value type
+ */
+public class MapValue<V> implements Comparable<MapValue<V>> {
+ private final Timestamp timestamp;
+ private final V value;
+
+ /**
+ * Creates a tombstone value with the specified timestamp.
+ * @param timestamp timestamp for tombstone
+ * @return tombstone MapValue
+ *
+ * @param <U> value type
+ */
+ public static <U> MapValue<U> tombstone(Timestamp timestamp) {
+ return new MapValue<>(null, timestamp);
+ }
+
+ public MapValue(V value, Timestamp timestamp) {
+ this.value = value;
+ this.timestamp = timestamp;
+ }
+
+ public boolean isTombstone() {
+ return value == null;
+ }
+
+ public boolean isAlive() {
+ return value != null;
+ }
+
+ public Timestamp timestamp() {
+ return timestamp;
+ }
+
+ public V get() {
+ return value;
+ }
+
+ @Override
+ public int compareTo(MapValue<V> o) {
+ return this.timestamp.compareTo(o.timestamp);
+ }
+
+ public boolean isNewerThan(MapValue<V> other) {
+ return timestamp.isNewerThan(other.timestamp);
+ }
+
+ public boolean isNewerThan(Timestamp timestamp) {
+ return this.timestamp.isNewerThan(timestamp);
+ }
+
+ public Digest digest() {
+ return new Digest(timestamp, isTombstone());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(timestamp, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof MapValue) {
+ MapValue<V> that = (MapValue<V>) other;
+ return Objects.equal(this.timestamp, that.timestamp) &&
+ Objects.equal(this.value, that.value);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("timestamp", timestamp)
+ .add("value", value)
+ .toString();
+ }
+
+ @SuppressWarnings("unused")
+ private MapValue() {
+ this.timestamp = null;
+ this.value = null;
+ }
+
+ /**
+ * Digest or summary of a MapValue for use during Anti-Entropy exchanges.
+ */
+ public static class Digest {
+ private final Timestamp timestamp;
+ private final boolean isTombstone;
+
+ public Digest(Timestamp timestamp, boolean isTombstone) {
+ this.timestamp = timestamp;
+ this.isTombstone = isTombstone;
+ }
+
+ public Timestamp timestamp() {
+ return timestamp;
+ }
+
+ public boolean isTombstone() {
+ return isTombstone;
+ }
+
+ public boolean isNewerThan(Digest other) {
+ return timestamp.isNewerThan(other.timestamp);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(timestamp, isTombstone);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Digest) {
+ Digest that = (Digest) other;
+ return Objects.equal(this.timestamp, that.timestamp) &&
+ Objects.equal(this.isTombstone, that.isTombstone);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("timestamp", timestamp)
+ .add("isTombstone", isTombstone)
+ .toString();
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/PersistentStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/PersistentStore.java
new file mode 100644
index 00000000..e85987a7
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/PersistentStore.java
@@ -0,0 +1,47 @@
+/*
+ * 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.store.ecmap;
+
+import java.util.Map;
+
+/**
+ * A persistent store for an eventually consistent map.
+ */
+interface PersistentStore<K, V> {
+
+ /**
+ * Read the contents of the disk into the given maps.
+ *
+ * @param items items map
+ */
+ void readInto(Map<K, MapValue<V>> items);
+
+ /**
+ * Updates a key,value pair in the persistent store.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ void update(K key, MapValue<V> value);
+
+ /**
+ * Removes a key from persistent store.
+ *
+ * @param key the key to remove
+ */
+ void remove(K key);
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/UpdateEntry.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/UpdateEntry.java
new file mode 100644
index 00000000..53683b98
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/UpdateEntry.java
@@ -0,0 +1,80 @@
+/*
+ * 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.store.ecmap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Describes a single update event in an EventuallyConsistentMap.
+ */
+final class UpdateEntry<K, V> {
+ private final K key;
+ private final MapValue<V> value;
+
+ /**
+ * Creates a new update entry.
+ *
+ * @param key key of the entry
+ * @param value value of the entry
+ */
+ public UpdateEntry(K key, MapValue<V> value) {
+ this.key = checkNotNull(key);
+ this.value = value;
+ }
+
+ /**
+ * Returns the key.
+ *
+ * @return the key
+ */
+ public K key() {
+ return key;
+ }
+
+ /**
+ * Returns the value of the entry.
+ *
+ * @return the value
+ */
+ public MapValue<V> value() {
+ return value;
+ }
+
+ /**
+ * Returns if this entry is newer than other entry.
+ * @param other other entry
+ * @return true if this entry is newer; false otherwise
+ */
+ public boolean isNewerThan(UpdateEntry<K, V> other) {
+ return other == null || other.value == null || (value != null && value.isNewerThan(other.value));
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("key", key())
+ .add("value", value)
+ .toString();
+ }
+
+ @SuppressWarnings("unused")
+ private UpdateEntry() {
+ this.key = null;
+ this.value = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/package-info.java
new file mode 100644
index 00000000..81fd2868
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Distributed map with eventually-consistent update semantics and gossip
+ * based anti-entropy mechanism.
+ */
+package org.onosproject.store.ecmap; \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfo.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfo.java
new file mode 100644
index 00000000..6011c16c
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfo.java
@@ -0,0 +1,85 @@
+/*
+ * 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.store.flow;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+
+/**
+ * Class to represent placement information about Master/Backup copy.
+ */
+public final class ReplicaInfo {
+
+ private final Optional<NodeId> master;
+ private final List<NodeId> backups;
+
+ /**
+ * Creates a ReplicaInfo instance.
+ *
+ * @param master NodeId of the node where the master copy should be
+ * @param backups list of NodeId, where backup copies should be placed
+ */
+ public ReplicaInfo(NodeId master, List<NodeId> backups) {
+ this.master = Optional.fromNullable(master);
+ this.backups = checkNotNull(backups);
+ }
+
+ /**
+ * Returns the NodeId, if there is a Node where the master copy should be.
+ *
+ * @return NodeId, where the master copy should be placed
+ */
+ public Optional<NodeId> master() {
+ return master;
+ }
+
+ /**
+ * Returns the collection of NodeId, where backup copies should be placed.
+ *
+ * @return collection of NodeId, where backup copies should be placed
+ */
+ public List<NodeId> backups() {
+ return backups;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(master, backups);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof ReplicaInfo)) {
+ return false;
+ }
+ ReplicaInfo that = (ReplicaInfo) other;
+ return Objects.equal(this.master, that.master) &&
+ Objects.equal(this.backups, that.backups);
+ }
+
+ // for Serializer
+ private ReplicaInfo() {
+ this.master = Optional.absent();
+ this.backups = Collections.emptyList();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEvent.java
new file mode 100644
index 00000000..5eafc7ed
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEvent.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.flow;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Describes a device replicainfo event.
+ */
+public class ReplicaInfoEvent extends AbstractEvent<ReplicaInfoEvent.Type, DeviceId> {
+
+ private final ReplicaInfo replicaInfo;
+
+ /**
+ * Types of Replica info event.
+ */
+ public enum Type {
+ /**
+ * Event to notify that master placement should be changed.
+ */
+ MASTER_CHANGED,
+ //
+ BACKUPS_CHANGED,
+ }
+
+
+ /**
+ * Creates an event of a given type and for the specified device,
+ * and replica info.
+ *
+ * @param type replicainfo event type
+ * @param device event device subject
+ * @param replicaInfo replicainfo
+ */
+ public ReplicaInfoEvent(Type type, DeviceId device, ReplicaInfo replicaInfo) {
+ super(type, device);
+ this.replicaInfo = checkNotNull(replicaInfo);
+ }
+
+ /**
+ * Returns the current replica information for the subject.
+ *
+ * @return replica information for the subject
+ */
+ public ReplicaInfo replicaInfo() {
+ return replicaInfo;
+ };
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEventListener.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEventListener.java
new file mode 100644
index 00000000..b6761d1d
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoEventListener.java
@@ -0,0 +1,26 @@
+/*
+ * 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.store.flow;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving Replica placement information-related events.
+ */
+public interface ReplicaInfoEventListener extends EventListener<ReplicaInfoEvent> {
+
+}
+
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoService.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoService.java
new file mode 100644
index 00000000..bf60f931
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/ReplicaInfoService.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.store.flow;
+
+import org.onosproject.net.DeviceId;
+
+/**
+ * Service to return where the replica should be placed.
+ */
+public interface ReplicaInfoService {
+
+ // returns where it should be.
+ /**
+ * Returns the placement information for given Device.
+ *
+ * @param deviceId identifier of the device
+ * @return placement information
+ */
+ ReplicaInfo getReplicaInfoFor(DeviceId deviceId);
+
+ /**
+ * Adds the specified replica placement info change listener.
+ *
+ * @param listener the replica placement info change listener
+ */
+ void addListener(ReplicaInfoEventListener listener);
+
+ /**
+ * Removes the specified replica placement info change listener.
+ *
+ * @param listener the replica placement info change listener
+ */
+ void removeListener(ReplicaInfoEventListener listener);
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/FlowStoreMessageSubjects.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/FlowStoreMessageSubjects.java
new file mode 100644
index 00000000..041053cf
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/FlowStoreMessageSubjects.java
@@ -0,0 +1,43 @@
+/*
+ * 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.store.flow.impl;
+
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by DistributedFlowRuleStore peer-peer communication.
+ */
+public final class FlowStoreMessageSubjects {
+ private FlowStoreMessageSubjects() {}
+
+ public static final MessageSubject APPLY_BATCH_FLOWS
+ = new MessageSubject("peer-forward-apply-batch");
+
+ public static final MessageSubject GET_FLOW_ENTRY
+ = new MessageSubject("peer-forward-get-flow-entry");
+
+ public static final MessageSubject GET_DEVICE_FLOW_ENTRIES
+ = new MessageSubject("peer-forward-get-device-flow-entries");
+
+ public static final MessageSubject REMOVE_FLOW_ENTRY
+ = new MessageSubject("peer-forward-remove-flow-entry");
+
+ public static final MessageSubject REMOTE_APPLY_COMPLETED
+ = new MessageSubject("peer-apply-completed");
+
+ public static final MessageSubject FLOW_TABLE_BACKUP
+ = new MessageSubject("peer-flow-table-backup");
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java
new file mode 100644
index 00000000..de7a3ac3
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java
@@ -0,0 +1,789 @@
+ /*
+ * 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.store.flow.impl;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+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.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.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.mastership.MastershipService;
+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.FlowEntry.FlowEntryState;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleBatchEntry;
+import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+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.FlowRuleEvent.Type;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.FlowRuleStore;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.flow.ReplicaInfoEvent;
+import org.onosproject.store.flow.ReplicaInfoEventListener;
+import org.onosproject.store.flow.ReplicaInfoService;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.serializers.StoreSerializer;
+import org.onosproject.store.serializers.custom.DistributedStoreSerializers;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.onosproject.store.flow.impl.FlowStoreMessageSubjects.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of flow rules using a distributed state management protocol.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class NewDistributedFlowRuleStore
+ extends AbstractStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+ implements FlowRuleStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final int MESSAGE_HANDLER_THREAD_POOL_SIZE = 8;
+ private static final boolean DEFAULT_BACKUP_ENABLED = true;
+ private static final int DEFAULT_BACKUP_PERIOD_MILLIS = 2000;
+ private static final long FLOW_RULE_STORE_TIMEOUT_MILLIS = 5000;
+ // number of devices whose flow entries will be backed up in one communication round
+ private static final int FLOW_TABLE_BACKUP_BATCH_SIZE = 1;
+
+ @Property(name = "msgHandlerPoolSize", intValue = MESSAGE_HANDLER_THREAD_POOL_SIZE,
+ label = "Number of threads in the message handler pool")
+ private int msgHandlerPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE;
+
+ @Property(name = "backupEnabled", boolValue = DEFAULT_BACKUP_ENABLED,
+ label = "Indicates whether backups are enabled or not")
+ private boolean backupEnabled = DEFAULT_BACKUP_ENABLED;
+
+ @Property(name = "backupPeriod", intValue = DEFAULT_BACKUP_PERIOD_MILLIS,
+ label = "Delay in ms between successive backup runs")
+ private int backupPeriod = DEFAULT_BACKUP_PERIOD_MILLIS;
+
+ private InternalFlowTable flowTable = new InternalFlowTable();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ReplicaInfoService replicaInfoManager;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ private Map<Long, NodeId> pendingResponses = Maps.newConcurrentMap();
+ private ExecutorService messageHandlingExecutor;
+
+ private ScheduledFuture<?> backupTask;
+ private final ScheduledExecutorService backupSenderExecutor =
+ Executors.newSingleThreadScheduledExecutor(groupedThreads("onos/flow", "backup-sender"));
+
+ protected static final StoreSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(DistributedStoreSerializers.STORE_COMMON)
+ .nextId(DistributedStoreSerializers.STORE_CUSTOM_BEGIN)
+ .build();
+ }
+ };
+
+ private IdGenerator idGenerator;
+ private NodeId local;
+
+ @Activate
+ public void activate(ComponentContext context) {
+ configService.registerProperties(getClass());
+
+ idGenerator = coreService.getIdGenerator(FlowRuleService.FLOW_OP_TOPIC);
+
+ local = clusterService.getLocalNode().id();
+
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ msgHandlerPoolSize, groupedThreads("onos/store/flow", "message-handlers"));
+
+ registerMessageHandlers(messageHandlingExecutor);
+
+ if (backupEnabled) {
+ replicaInfoManager.addListener(flowTable);
+ backupTask = backupSenderExecutor.scheduleWithFixedDelay(
+ flowTable::backup,
+ 0,
+ backupPeriod,
+ TimeUnit.MILLISECONDS);
+ }
+
+ logConfig("Started");
+ }
+
+ @Deactivate
+ public void deactivate(ComponentContext context) {
+ if (backupEnabled) {
+ replicaInfoManager.removeListener(flowTable);
+ backupTask.cancel(true);
+ }
+ configService.unregisterProperties(getClass(), false);
+ unregisterMessageHandlers();
+ messageHandlingExecutor.shutdownNow();
+ backupSenderExecutor.shutdownNow();
+ log.info("Stopped");
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Modified
+ public void modified(ComponentContext context) {
+ if (context == null) {
+ backupEnabled = DEFAULT_BACKUP_ENABLED;
+ logConfig("Default config");
+ return;
+ }
+
+ Dictionary properties = context.getProperties();
+ int newPoolSize;
+ boolean newBackupEnabled;
+ int newBackupPeriod;
+ try {
+ String s = get(properties, "msgHandlerPoolSize");
+ newPoolSize = isNullOrEmpty(s) ? msgHandlerPoolSize : Integer.parseInt(s.trim());
+
+ s = get(properties, "backupEnabled");
+ newBackupEnabled = isNullOrEmpty(s) ? backupEnabled : Boolean.parseBoolean(s.trim());
+
+ s = get(properties, "backupPeriod");
+ newBackupPeriod = isNullOrEmpty(s) ? backupPeriod : Integer.parseInt(s.trim());
+
+ } catch (NumberFormatException | ClassCastException e) {
+ newPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE;
+ newBackupEnabled = DEFAULT_BACKUP_ENABLED;
+ newBackupPeriod = DEFAULT_BACKUP_PERIOD_MILLIS;
+ }
+
+ boolean restartBackupTask = false;
+ if (newBackupEnabled != backupEnabled) {
+ backupEnabled = newBackupEnabled;
+ if (!backupEnabled) {
+ replicaInfoManager.removeListener(flowTable);
+ if (backupTask != null) {
+ backupTask.cancel(false);
+ backupTask = null;
+ }
+ } else {
+ replicaInfoManager.addListener(flowTable);
+ }
+ restartBackupTask = backupEnabled;
+ }
+ if (newBackupPeriod != backupPeriod) {
+ backupPeriod = newBackupPeriod;
+ restartBackupTask = backupEnabled;
+ }
+ if (restartBackupTask) {
+ if (backupTask != null) {
+ // cancel previously running task
+ backupTask.cancel(false);
+ }
+ backupTask = backupSenderExecutor.scheduleWithFixedDelay(
+ flowTable::backup,
+ 0,
+ backupPeriod,
+ TimeUnit.MILLISECONDS);
+ }
+ if (newPoolSize != msgHandlerPoolSize) {
+ msgHandlerPoolSize = newPoolSize;
+ ExecutorService oldMsgHandler = messageHandlingExecutor;
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ msgHandlerPoolSize, groupedThreads("onos/store/flow", "message-handlers"));
+
+ // replace previously registered handlers.
+ registerMessageHandlers(messageHandlingExecutor);
+ oldMsgHandler.shutdown();
+ }
+ logConfig("Reconfigured");
+ }
+
+ private void registerMessageHandlers(ExecutorService executor) {
+
+ clusterCommunicator.addSubscriber(APPLY_BATCH_FLOWS, new OnStoreBatch(), executor);
+ clusterCommunicator.<FlowRuleBatchEvent>addSubscriber(
+ REMOTE_APPLY_COMPLETED, SERIALIZER::decode, this::notifyDelegate, executor);
+ clusterCommunicator.addSubscriber(
+ GET_FLOW_ENTRY, SERIALIZER::decode, flowTable::getFlowEntry, SERIALIZER::encode, executor);
+ clusterCommunicator.addSubscriber(
+ GET_DEVICE_FLOW_ENTRIES, SERIALIZER::decode, flowTable::getFlowEntries, SERIALIZER::encode, executor);
+ clusterCommunicator.addSubscriber(
+ REMOVE_FLOW_ENTRY, SERIALIZER::decode, this::removeFlowRuleInternal, SERIALIZER::encode, executor);
+ clusterCommunicator.addSubscriber(
+ REMOVE_FLOW_ENTRY, SERIALIZER::decode, this::removeFlowRuleInternal, SERIALIZER::encode, executor);
+ clusterCommunicator.addSubscriber(
+ FLOW_TABLE_BACKUP, SERIALIZER::decode, flowTable::onBackupReceipt, SERIALIZER::encode, executor);
+ }
+
+ private void unregisterMessageHandlers() {
+ clusterCommunicator.removeSubscriber(REMOVE_FLOW_ENTRY);
+ clusterCommunicator.removeSubscriber(GET_DEVICE_FLOW_ENTRIES);
+ clusterCommunicator.removeSubscriber(GET_FLOW_ENTRY);
+ clusterCommunicator.removeSubscriber(APPLY_BATCH_FLOWS);
+ clusterCommunicator.removeSubscriber(REMOTE_APPLY_COMPLETED);
+ clusterCommunicator.removeSubscriber(FLOW_TABLE_BACKUP);
+ }
+
+ private void logConfig(String prefix) {
+ log.info("{} with msgHandlerPoolSize = {}; backupEnabled = {}, backupPeriod = {}",
+ prefix, msgHandlerPoolSize, backupEnabled, backupPeriod);
+ }
+
+ // This is not a efficient operation on a distributed sharded
+ // flow store. We need to revisit the need for this operation or at least
+ // make it device specific.
+ @Override
+ public int getFlowRuleCount() {
+ AtomicInteger sum = new AtomicInteger(0);
+ deviceService.getDevices().forEach(device -> sum.addAndGet(Iterables.size(getFlowEntries(device.id()))));
+ return sum.get();
+ }
+
+ @Override
+ public FlowEntry getFlowEntry(FlowRule rule) {
+ NodeId master = mastershipService.getMasterFor(rule.deviceId());
+
+ if (master == null) {
+ log.debug("Failed to getFlowEntry: No master for {}", rule.deviceId());
+ return null;
+ }
+
+ if (Objects.equal(local, master)) {
+ return flowTable.getFlowEntry(rule);
+ }
+
+ log.trace("Forwarding getFlowEntry to {}, which is the primary (master) for device {}",
+ master, rule.deviceId());
+
+ return Tools.futureGetOrElse(clusterCommunicator.sendAndReceive(rule,
+ FlowStoreMessageSubjects.GET_FLOW_ENTRY,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master),
+ FLOW_RULE_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ null);
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (master == null) {
+ log.debug("Failed to getFlowEntries: No master for {}", deviceId);
+ return Collections.emptyList();
+ }
+
+ if (Objects.equal(local, master)) {
+ return flowTable.getFlowEntries(deviceId);
+ }
+
+ log.trace("Forwarding getFlowEntries to {}, which is the primary (master) for device {}",
+ master, deviceId);
+
+ return Tools.futureGetOrElse(clusterCommunicator.sendAndReceive(deviceId,
+ FlowStoreMessageSubjects.GET_DEVICE_FLOW_ENTRIES,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master),
+ FLOW_RULE_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ Collections.emptyList());
+ }
+
+ @Override
+ public void storeFlowRule(FlowRule rule) {
+ storeBatch(new FlowRuleBatchOperation(
+ Collections.singletonList(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule)),
+ rule.deviceId(), idGenerator.getNewId()));
+ }
+
+ @Override
+ public void storeBatch(FlowRuleBatchOperation operation) {
+ if (operation.getOperations().isEmpty()) {
+ notifyDelegate(FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(), operation.deviceId())));
+ return;
+ }
+
+ DeviceId deviceId = operation.deviceId();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (master == null) {
+ log.warn("No master for {} : flows will be marked for removal", deviceId);
+
+ updateStoreInternal(operation);
+
+ notifyDelegate(FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(), operation.deviceId())));
+ return;
+ }
+
+ if (Objects.equal(local, master)) {
+ storeBatchInternal(operation);
+ return;
+ }
+
+ log.trace("Forwarding storeBatch to {}, which is the primary (master) for device {}",
+ master, deviceId);
+
+ clusterCommunicator.unicast(operation,
+ APPLY_BATCH_FLOWS,
+ SERIALIZER::encode,
+ master)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ log.warn("Failed to storeBatch: {} to {}", operation, master, error);
+
+ Set<FlowRule> allFailures = operation.getOperations()
+ .stream()
+ .map(op -> op.target())
+ .collect(Collectors.toSet());
+
+ notifyDelegate(FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(false, allFailures, deviceId)));
+ }
+ });
+ }
+
+ private void storeBatchInternal(FlowRuleBatchOperation operation) {
+
+ final DeviceId did = operation.deviceId();
+ //final Collection<FlowEntry> ft = flowTable.getFlowEntries(did);
+ Set<FlowRuleBatchEntry> currentOps = updateStoreInternal(operation);
+ if (currentOps.isEmpty()) {
+ batchOperationComplete(FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(), did)));
+ return;
+ }
+
+ notifyDelegate(FlowRuleBatchEvent.requested(new
+ FlowRuleBatchRequest(operation.id(),
+ currentOps), operation.deviceId()));
+ }
+
+ private Set<FlowRuleBatchEntry> updateStoreInternal(FlowRuleBatchOperation operation) {
+ return operation.getOperations().stream().map(
+ op -> {
+ StoredFlowEntry entry;
+ switch (op.operator()) {
+ case ADD:
+ entry = new DefaultFlowEntry(op.target());
+ // always add requested FlowRule
+ // Note: 2 equal FlowEntry may have different treatment
+ flowTable.remove(entry.deviceId(), entry);
+ flowTable.add(entry);
+
+ return op;
+ case REMOVE:
+ entry = flowTable.getFlowEntry(op.target());
+ if (entry != null) {
+ entry.setState(FlowEntryState.PENDING_REMOVE);
+ return op;
+ }
+ break;
+ case MODIFY:
+ //TODO: figure this out at some point
+ break;
+ default:
+ log.warn("Unknown flow operation operator: {}", op.operator());
+ }
+ return null;
+ }
+ ).filter(op -> op != null).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void deleteFlowRule(FlowRule rule) {
+ storeBatch(
+ new FlowRuleBatchOperation(
+ Collections.singletonList(
+ new FlowRuleBatchEntry(
+ FlowRuleOperation.REMOVE,
+ rule)), rule.deviceId(), idGenerator.getNewId()));
+ }
+
+ @Override
+ public FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
+ NodeId master = mastershipService.getMasterFor(rule.deviceId());
+ if (Objects.equal(local, master)) {
+ return addOrUpdateFlowRuleInternal(rule);
+ }
+
+ log.warn("Tried to update FlowRule {} state,"
+ + " while the Node was not the master.", rule);
+ return null;
+ }
+
+ private FlowRuleEvent addOrUpdateFlowRuleInternal(FlowEntry rule) {
+ // check if this new rule is an update to an existing entry
+ StoredFlowEntry stored = flowTable.getFlowEntry(rule);
+ if (stored != null) {
+ stored.setBytes(rule.bytes());
+ stored.setLife(rule.life());
+ stored.setPackets(rule.packets());
+ if (stored.state() == FlowEntryState.PENDING_ADD) {
+ stored.setState(FlowEntryState.ADDED);
+ return new FlowRuleEvent(Type.RULE_ADDED, rule);
+ }
+ return new FlowRuleEvent(Type.RULE_UPDATED, rule);
+ }
+
+ // TODO: Confirm if this behavior is correct. See SimpleFlowRuleStore
+ // TODO: also update backup if the behavior is correct.
+ flowTable.add(rule);
+ return null;
+ }
+
+ @Override
+ public FlowRuleEvent removeFlowRule(FlowEntry rule) {
+ final DeviceId deviceId = rule.deviceId();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (Objects.equal(local, master)) {
+ // bypass and handle it locally
+ return removeFlowRuleInternal(rule);
+ }
+
+ if (master == null) {
+ log.warn("Failed to removeFlowRule: No master for {}", deviceId);
+ // TODO: revisit if this should be null (="no-op") or Exception
+ return null;
+ }
+
+ log.trace("Forwarding removeFlowRule to {}, which is the master for device {}",
+ master, deviceId);
+
+ return Futures.get(clusterCommunicator.sendAndReceive(
+ rule,
+ REMOVE_FLOW_ENTRY,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master),
+ FLOW_RULE_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ RuntimeException.class);
+ }
+
+ private FlowRuleEvent removeFlowRuleInternal(FlowEntry rule) {
+ final DeviceId deviceId = rule.deviceId();
+ // This is where one could mark a rule as removed and still keep it in the store.
+ final boolean removed = flowTable.remove(deviceId, rule); //flowEntries.remove(deviceId, rule);
+ return removed ? new FlowRuleEvent(RULE_REMOVED, rule) : null;
+ }
+
+ @Override
+ public void batchOperationComplete(FlowRuleBatchEvent event) {
+ //FIXME: need a per device pending response
+ NodeId nodeId = pendingResponses.remove(event.subject().batchId());
+ if (nodeId == null) {
+ notifyDelegate(event);
+ } else {
+ // TODO check unicast return value
+ clusterCommunicator.unicast(event, REMOTE_APPLY_COMPLETED, SERIALIZER::encode, nodeId);
+ //error log: log.warn("Failed to respond to peer for batch operation result");
+ }
+ }
+
+ private final class OnStoreBatch implements ClusterMessageHandler {
+
+ @Override
+ public void handle(final ClusterMessage message) {
+ FlowRuleBatchOperation operation = SERIALIZER.decode(message.payload());
+ log.debug("received batch request {}", operation);
+
+ final DeviceId deviceId = operation.deviceId();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ if (!Objects.equal(local, master)) {
+ Set<FlowRule> failures = new HashSet<>(operation.size());
+ for (FlowRuleBatchEntry op : operation.getOperations()) {
+ failures.add(op.target());
+ }
+ CompletedBatchOperation allFailed = new CompletedBatchOperation(false, failures, deviceId);
+ // This node is no longer the master, respond as all failed.
+ // TODO: we might want to wrap response in envelope
+ // to distinguish sw programming failure and hand over
+ // it make sense in the latter case to retry immediately.
+ message.respond(SERIALIZER.encode(allFailed));
+ return;
+ }
+
+ pendingResponses.put(operation.id(), message.sender());
+ storeBatchInternal(operation);
+ }
+ }
+
+ private class InternalFlowTable implements ReplicaInfoEventListener {
+
+ private final Map<DeviceId, Map<FlowId, Set<StoredFlowEntry>>>
+ flowEntries = Maps.newConcurrentMap();
+
+ private final Map<DeviceId, Long> lastBackupTimes = Maps.newConcurrentMap();
+ private final Map<DeviceId, Long> lastUpdateTimes = Maps.newConcurrentMap();
+ private final Map<DeviceId, NodeId> lastBackupNodes = Maps.newConcurrentMap();
+
+ @Override
+ public void event(ReplicaInfoEvent event) {
+ if (!backupEnabled) {
+ return;
+ }
+ if (event.type() == ReplicaInfoEvent.Type.BACKUPS_CHANGED) {
+ DeviceId deviceId = event.subject();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ if (!Objects.equal(local, master)) {
+ // ignore since this event is for a device this node does not manage.
+ return;
+ }
+ NodeId newBackupNode = getBackupNode(deviceId);
+ NodeId currentBackupNode = lastBackupNodes.get(deviceId);
+ if (Objects.equal(newBackupNode, currentBackupNode)) {
+ // ignore since backup location hasn't changed.
+ return;
+ }
+ if (currentBackupNode != null && newBackupNode == null) {
+ // Current backup node is most likely down and no alternate backup node
+ // has been chosen. Clear current backup location so that we can resume
+ // backups when either current backup comes online or a different backup node
+ // is chosen.
+ log.warn("Lost backup location {} for deviceId {} and no alternate backup node exists. "
+ + "Flows can be lost if the master goes down", currentBackupNode, deviceId);
+ lastBackupNodes.remove(deviceId);
+ lastBackupTimes.remove(deviceId);
+ return;
+ // TODO: Pick any available node as backup and ensure hand-off occurs when
+ // a new master is elected.
+ }
+ log.debug("Backup location for {} has changed from {} to {}.",
+ deviceId, currentBackupNode, newBackupNode);
+ backupSenderExecutor.schedule(() -> backupFlowEntries(newBackupNode, Sets.newHashSet(deviceId)),
+ 0,
+ TimeUnit.SECONDS);
+ }
+ }
+
+ private void sendBackups(NodeId nodeId, Set<DeviceId> deviceIds) {
+ // split up the devices into smaller batches and send them separately.
+ Iterables.partition(deviceIds, FLOW_TABLE_BACKUP_BATCH_SIZE)
+ .forEach(ids -> backupFlowEntries(nodeId, Sets.newHashSet(ids)));
+ }
+
+ private void backupFlowEntries(NodeId nodeId, Set<DeviceId> deviceIds) {
+ if (deviceIds.isEmpty()) {
+ return;
+ }
+ log.debug("Sending flowEntries for devices {} to {} as backup.", deviceIds, nodeId);
+ Map<DeviceId, Map<FlowId, Set<StoredFlowEntry>>> deviceFlowEntries =
+ Maps.newConcurrentMap();
+ deviceIds.forEach(id -> deviceFlowEntries.put(id, ImmutableMap.copyOf(getFlowTable(id))));
+ clusterCommunicator.<Map<DeviceId, Map<FlowId, Set<StoredFlowEntry>>>, Set<DeviceId>>sendAndReceive(
+ deviceFlowEntries,
+ FLOW_TABLE_BACKUP,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ nodeId)
+ .whenComplete((backedupDevices, error) -> {
+ Set<DeviceId> devicesNotBackedup = error != null ?
+ deviceFlowEntries.keySet() :
+ Sets.difference(deviceFlowEntries.keySet(), backedupDevices);
+ if (devicesNotBackedup.size() > 0) {
+ log.warn("Failed to backup devices: {}. Reason: {}",
+ devicesNotBackedup, error.getMessage());
+ }
+ if (backedupDevices != null) {
+ backedupDevices.forEach(id -> {
+ lastBackupTimes.put(id, System.currentTimeMillis());
+ lastBackupNodes.put(id, nodeId);
+ });
+ }
+ });
+ }
+
+ /**
+ * Returns the flow table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing Flow Table of given device.
+ */
+ private Map<FlowId, Set<StoredFlowEntry>> getFlowTable(DeviceId deviceId) {
+ return flowEntries.computeIfAbsent(deviceId, id -> Maps.newConcurrentMap());
+ }
+
+ private Set<StoredFlowEntry> getFlowEntriesInternal(DeviceId deviceId, FlowId flowId) {
+ return getFlowTable(deviceId).computeIfAbsent(flowId, id -> Sets.newCopyOnWriteArraySet());
+ }
+
+ private StoredFlowEntry getFlowEntryInternal(FlowRule rule) {
+ Set<StoredFlowEntry> flowEntries = getFlowEntriesInternal(rule.deviceId(), rule.id());
+ return flowEntries.stream()
+ .filter(entry -> Objects.equal(entry, rule))
+ .findAny()
+ .orElse(null);
+ }
+
+ private Set<FlowEntry> getFlowEntriesInternal(DeviceId deviceId) {
+ Set<FlowEntry> result = Sets.newHashSet();
+ getFlowTable(deviceId).values().forEach(result::addAll);
+ return result;
+ }
+
+ public StoredFlowEntry getFlowEntry(FlowRule rule) {
+ return getFlowEntryInternal(rule);
+ }
+
+ public Set<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ return getFlowEntriesInternal(deviceId);
+ }
+
+ public void add(FlowEntry rule) {
+ getFlowEntriesInternal(rule.deviceId(), rule.id()).add((StoredFlowEntry) rule);
+ lastUpdateTimes.put(rule.deviceId(), System.currentTimeMillis());
+ }
+
+ public boolean remove(DeviceId deviceId, FlowEntry rule) {
+ try {
+ return getFlowEntriesInternal(deviceId, rule.id()).remove(rule);
+ } finally {
+ lastUpdateTimes.put(deviceId, System.currentTimeMillis());
+ }
+ }
+
+ private NodeId getBackupNode(DeviceId deviceId) {
+ List<NodeId> deviceStandbys = replicaInfoManager.getReplicaInfoFor(deviceId).backups();
+ // pick the standby which is most likely to become next master
+ return deviceStandbys.isEmpty() ? null : deviceStandbys.get(0);
+ }
+
+ private void backup() {
+ if (!backupEnabled) {
+ return;
+ }
+ try {
+ // determine the set of devices that we need to backup during this run.
+ Set<DeviceId> devicesToBackup = mastershipService.getDevicesOf(local)
+ .stream()
+ .filter(deviceId -> {
+ Long lastBackupTime = lastBackupTimes.get(deviceId);
+ Long lastUpdateTime = lastUpdateTimes.get(deviceId);
+ NodeId lastBackupNode = lastBackupNodes.get(deviceId);
+ NodeId newBackupNode = getBackupNode(deviceId);
+ return lastBackupTime == null
+ || !Objects.equal(lastBackupNode, newBackupNode)
+ || (lastUpdateTime != null && lastUpdateTime > lastBackupTime);
+ })
+ .collect(Collectors.toSet());
+
+ // compute a mapping from node to the set of devices whose flow entries it should backup
+ Map<NodeId, Set<DeviceId>> devicesToBackupByNode = Maps.newHashMap();
+ devicesToBackup.forEach(deviceId -> {
+ NodeId backupLocation = getBackupNode(deviceId);
+ if (backupLocation != null) {
+ devicesToBackupByNode.computeIfAbsent(backupLocation, nodeId -> Sets.newHashSet())
+ .add(deviceId);
+ }
+ });
+ // send the device flow entries to their respective backup nodes
+ devicesToBackupByNode.forEach(this::sendBackups);
+ } catch (Exception e) {
+ log.error("Backup failed.", e);
+ }
+ }
+
+ private Set<DeviceId> onBackupReceipt(Map<DeviceId, Map<FlowId, Set<StoredFlowEntry>>> flowTables) {
+ log.debug("Received flowEntries for {} to backup", flowTables.keySet());
+ Set<DeviceId> backedupDevices = Sets.newHashSet();
+ try {
+ flowTables.forEach((deviceId, deviceFlowTable) -> {
+ // Only process those devices are that not managed by the local node.
+ if (!Objects.equal(local, mastershipService.getMasterFor(deviceId))) {
+ Map<FlowId, Set<StoredFlowEntry>> backupFlowTable = getFlowTable(deviceId);
+ backupFlowTable.clear();
+ backupFlowTable.putAll(deviceFlowTable);
+ backedupDevices.add(deviceId);
+ }
+ });
+ } catch (Exception e) {
+ log.warn("Failure processing backup request", e);
+ }
+ return backedupDevices;
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/ReplicaInfoManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/ReplicaInfoManager.java
new file mode 100644
index 00000000..ebb487bf
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/ReplicaInfoManager.java
@@ -0,0 +1,123 @@
+/*
+ * 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.store.flow.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.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.flow.ReplicaInfo;
+import org.onosproject.store.flow.ReplicaInfoEvent;
+import org.onosproject.store.flow.ReplicaInfoEventListener;
+import org.onosproject.store.flow.ReplicaInfoService;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.List;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.store.flow.ReplicaInfoEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.store.flow.ReplicaInfoEvent.Type.MASTER_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages replica placement information.
+ */
+@Component(immediate = true)
+@Service
+public class ReplicaInfoManager implements ReplicaInfoService {
+
+ private final Logger log = getLogger(getClass());
+
+ private final MastershipListener mastershipListener = new InternalMastershipListener();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ protected final ListenerRegistry<ReplicaInfoEvent, ReplicaInfoEventListener>
+ listenerRegistry = new ListenerRegistry<>();
+
+ @Activate
+ public void activate() {
+ eventDispatcher.addSink(ReplicaInfoEvent.class, listenerRegistry);
+ mastershipService.addListener(mastershipListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(ReplicaInfoEvent.class);
+ mastershipService.removeListener(mastershipListener);
+ log.info("Stopped");
+ }
+
+ @Override
+ public ReplicaInfo getReplicaInfoFor(DeviceId deviceId) {
+ return buildFromRoleInfo(mastershipService.getNodesFor(deviceId));
+ }
+
+ @Override
+ public void addListener(ReplicaInfoEventListener listener) {
+ listenerRegistry.addListener(checkNotNull(listener));
+ }
+
+ @Override
+ public void removeListener(ReplicaInfoEventListener listener) {
+ listenerRegistry.removeListener(checkNotNull(listener));
+ }
+
+ private static ReplicaInfo buildFromRoleInfo(RoleInfo roles) {
+ List<NodeId> backups = roles.backups() == null ?
+ Collections.emptyList() : ImmutableList.copyOf(roles.backups());
+ return new ReplicaInfo(roles.master(), backups);
+ }
+
+ final class InternalMastershipListener implements MastershipListener {
+
+ @Override
+ public void event(MastershipEvent event) {
+ final ReplicaInfo replicaInfo = buildFromRoleInfo(event.roleInfo());
+ switch (event.type()) {
+ case MASTER_CHANGED:
+ eventDispatcher.post(new ReplicaInfoEvent(MASTER_CHANGED,
+ event.subject(),
+ replicaInfo));
+ break;
+ case BACKUPS_CHANGED:
+ eventDispatcher.post(new ReplicaInfoEvent(BACKUPS_CHANGED,
+ event.subject(),
+ replicaInfo));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/impl/package-info.java
new file mode 100644
index 00000000..b3de23db
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/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.
+ */
+
+/**
+ * Implementation of the distributed flow rule store using p2p synchronization
+ * protocol.
+ */
+package org.onosproject.store.flow.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/package-info.java
new file mode 100644
index 00000000..10dd24e3
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flow/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.
+ */
+
+/**
+ * Definitions of events and messages pertaining to replication of flow entries.
+ */
+package org.onosproject.store.flow;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStore.java
new file mode 100644
index 00000000..e8ea24fa
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStore.java
@@ -0,0 +1,102 @@
+/*
+ * 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.store.flowobjective.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.util.KryoNamespace;
+import org.onosproject.net.behaviour.DefaultNextGroup;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate;
+import org.onosproject.net.flowobjective.ObjectiveEvent;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of created next groups.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class DistributedFlowObjectiveStore
+ extends AbstractStore<ObjectiveEvent, FlowObjectiveStoreDelegate>
+ implements FlowObjectiveStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private ConsistentMap<Integer, byte[]> nextGroups;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private AtomicCounter nextIds;
+
+ @Activate
+ public void activate() {
+ nextGroups = storageService.<Integer, byte[]>consistentMapBuilder()
+ .withName("flowobjective-groups")
+ .withSerializer(Serializer.using(
+ new KryoNamespace.Builder()
+ .register(byte[].class)
+ .register(Versioned.class)
+ .build()))
+ .build();
+
+ nextIds = storageService.atomicCounterBuilder()
+ .withName("next-objective-counter")
+ .build();
+
+ log.info("Started");
+ }
+
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+
+ @Override
+ public void putNextGroup(Integer nextId, NextGroup group) {
+ nextGroups.putIfAbsent(nextId, group.data());
+ notifyDelegate(new ObjectiveEvent(ObjectiveEvent.Type.ADD, nextId));
+ }
+
+ @Override
+ public NextGroup getNextGroup(Integer nextId) {
+ Versioned<byte[]> versionGroup = nextGroups.get(nextId);
+ if (versionGroup != null) {
+ return new DefaultNextGroup(versionGroup.value());
+ }
+ return null;
+ }
+
+ @Override
+ public int allocateNextId() {
+ return (int) nextIds.incrementAndGet();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/flowobjective/impl/package-info.java
new file mode 100644
index 00000000..49acd878
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of distributed store for the flow objective state.
+ */
+package org.onosproject.store.flowobjective.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
new file mode 100644
index 00000000..cf48dcb8
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
@@ -0,0 +1,1304 @@
+/*
+ * 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.store.group.impl;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+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.util.KryoNamespace;
+import org.onlab.util.NewConcurrentHashMap;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.instructions.Instructions;
+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.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.Group.GroupState;
+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.GroupEvent.Type;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.service.MultiValuedTimestamp;
+import org.onosproject.store.serializers.DeviceIdSerializer;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.URISerializer;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapBuilder;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of group entries using trivial in-memory implementation.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedGroupStore
+ extends AbstractStore<GroupEvent, GroupStoreDelegate>
+ implements GroupStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final int dummyId = 0xffffffff;
+ private final GroupId dummyGroupId = new DefaultGroupId(dummyId);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ // Per device group table with (device id + app cookie) as key
+ private EventuallyConsistentMap<GroupStoreKeyMapKey,
+ StoredGroupEntry> groupStoreEntriesByKey = null;
+ // Per device group table with (device id + group id) as key
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>
+ groupEntriesById = new ConcurrentHashMap<>();
+ private EventuallyConsistentMap<GroupStoreKeyMapKey,
+ StoredGroupEntry> auditPendingReqQueue = null;
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, Group>>
+ extraneousGroupEntriesById = new ConcurrentHashMap<>();
+ private ExecutorService messageHandlingExecutor;
+ private static final int MESSAGE_HANDLER_THREAD_POOL_SIZE = 1;
+
+ private final HashMap<DeviceId, Boolean> deviceAuditStatus =
+ new HashMap<DeviceId, Boolean>();
+
+ private final AtomicInteger groupIdGen = new AtomicInteger();
+
+ private KryoNamespace.Builder kryoBuilder = null;
+
+ private final AtomicLong sequenceNumber = new AtomicLong(0);
+
+ @Activate
+ public void activate() {
+ kryoBuilder = new KryoNamespace.Builder()
+ .register(DefaultGroup.class,
+ DefaultGroupBucket.class,
+ DefaultGroupDescription.class,
+ DefaultGroupKey.class,
+ GroupDescription.Type.class,
+ Group.GroupState.class,
+ GroupBuckets.class,
+ DefaultGroupId.class,
+ GroupStoreMessage.class,
+ GroupStoreMessage.Type.class,
+ UpdateType.class,
+ GroupStoreMessageSubjects.class,
+ MultiValuedTimestamp.class,
+ GroupStoreKeyMapKey.class,
+ GroupStoreIdMapKey.class,
+ GroupStoreMapKey.class
+ )
+ .register(new URISerializer(), URI.class)
+ .register(new DeviceIdSerializer(), DeviceId.class)
+ .register(PortNumber.class)
+ .register(DefaultApplicationId.class)
+ .register(DefaultTrafficTreatment.class,
+ Instructions.DropInstruction.class,
+ Instructions.OutputInstruction.class,
+ Instructions.GroupInstruction.class,
+ Instructions.TableTypeTransition.class,
+ FlowRule.Type.class,
+ L0ModificationInstruction.class,
+ L0ModificationInstruction.L0SubType.class,
+ L0ModificationInstruction.ModLambdaInstruction.class,
+ L2ModificationInstruction.class,
+ L2ModificationInstruction.L2SubType.class,
+ L2ModificationInstruction.ModEtherInstruction.class,
+ L2ModificationInstruction.PushHeaderInstructions.class,
+ L2ModificationInstruction.ModVlanIdInstruction.class,
+ L2ModificationInstruction.ModVlanPcpInstruction.class,
+ L2ModificationInstruction.ModMplsLabelInstruction.class,
+ L2ModificationInstruction.ModMplsTtlInstruction.class,
+ L3ModificationInstruction.class,
+ L3ModificationInstruction.L3SubType.class,
+ L3ModificationInstruction.ModIPInstruction.class,
+ L3ModificationInstruction.ModIPv6FlowLabelInstruction.class,
+ L3ModificationInstruction.ModTtlInstruction.class,
+ org.onlab.packet.MplsLabel.class
+ )
+ .register(org.onosproject.cluster.NodeId.class)
+ .register(KryoNamespaces.BASIC)
+ .register(KryoNamespaces.MISC);
+
+ messageHandlingExecutor = Executors.
+ newFixedThreadPool(MESSAGE_HANDLER_THREAD_POOL_SIZE,
+ groupedThreads("onos/store/group",
+ "message-handlers"));
+
+ clusterCommunicator.addSubscriber(GroupStoreMessageSubjects.REMOTE_GROUP_OP_REQUEST,
+ kryoBuilder.build()::deserialize,
+ this::process,
+ messageHandlingExecutor);
+
+ log.debug("Creating EC map groupstorekeymap");
+ EventuallyConsistentMapBuilder<GroupStoreKeyMapKey, StoredGroupEntry>
+ keyMapBuilder = storageService.eventuallyConsistentMapBuilder();
+
+ groupStoreEntriesByKey = keyMapBuilder
+ .withName("groupstorekeymap")
+ .withSerializer(kryoBuilder)
+ .withTimestampProvider((k, v) -> new MultiValuedTimestamp<>(System.currentTimeMillis(),
+ sequenceNumber.getAndIncrement()))
+ .build();
+ groupStoreEntriesByKey.addListener(new GroupStoreKeyMapListener());
+ log.debug("Current size of groupstorekeymap:{}",
+ groupStoreEntriesByKey.size());
+
+ log.debug("Creating EC map pendinggroupkeymap");
+ EventuallyConsistentMapBuilder<GroupStoreKeyMapKey, StoredGroupEntry>
+ auditMapBuilder = storageService.eventuallyConsistentMapBuilder();
+
+ auditPendingReqQueue = auditMapBuilder
+ .withName("pendinggroupkeymap")
+ .withSerializer(kryoBuilder)
+ .withTimestampProvider((k, v) -> new MultiValuedTimestamp<>(System.currentTimeMillis(),
+ sequenceNumber.getAndIncrement()))
+ .build();
+ log.debug("Current size of pendinggroupkeymap:{}",
+ auditPendingReqQueue.size());
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ groupStoreEntriesByKey.destroy();
+ auditPendingReqQueue.destroy();
+ log.info("Stopped");
+ }
+
+ private static NewConcurrentHashMap<GroupId, Group>
+ lazyEmptyExtraneousGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, Group>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupId, StoredGroupEntry>
+ lazyEmptyGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, StoredGroupEntry>ifNeeded();
+ }
+
+ /**
+ * Returns the group store eventual consistent key map.
+ *
+ * @return Map representing group key table.
+ */
+ private EventuallyConsistentMap<GroupStoreKeyMapKey, StoredGroupEntry>
+ getGroupStoreKeyMap() {
+ return groupStoreEntriesByKey;
+ }
+
+ /**
+ * Returns the group id table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupId, StoredGroupEntry> getGroupIdTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(groupEntriesById,
+ deviceId, lazyEmptyGroupIdTable());
+ }
+
+ /**
+ * Returns the pending group request table.
+ *
+ * @return Map representing group key table.
+ */
+ private EventuallyConsistentMap<GroupStoreKeyMapKey, StoredGroupEntry>
+ getPendingGroupKeyTable() {
+ return auditPendingReqQueue;
+ }
+
+ /**
+ * Returns the extraneous group id table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupId, Group>
+ getExtraneousGroupIdTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(extraneousGroupEntriesById,
+ deviceId,
+ lazyEmptyExtraneousGroupIdTable());
+ }
+
+ /**
+ * Returns the number of groups for the specified device in the store.
+ *
+ * @return number of groups for the specified device
+ */
+ @Override
+ public int getGroupCount(DeviceId deviceId) {
+ return (getGroups(deviceId) != null) ?
+ Iterables.size(getGroups(deviceId)) : 0;
+ }
+
+ /**
+ * Returns the groups associated with a device.
+ *
+ * @param deviceId the device ID
+ *
+ * @return the group entries
+ */
+ @Override
+ public Iterable<Group> getGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ log.debug("getGroups: for device {} total number of groups {}",
+ deviceId, getGroupStoreKeyMap().values().size());
+ return FluentIterable.from(getGroupStoreKeyMap().values())
+ .filter(input -> input.deviceId().equals(deviceId))
+ .transform(input -> input);
+ }
+
+ private Iterable<StoredGroupEntry> getStoredGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ log.debug("getGroups: for device {} total number of groups {}",
+ deviceId, getGroupStoreKeyMap().values().size());
+ return FluentIterable.from(getGroupStoreKeyMap().values())
+ .filter(input -> input.deviceId().equals(deviceId));
+ }
+
+ /**
+ * Returns the stored group entry.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ *
+ * @return a group associated with the key
+ */
+ @Override
+ public Group getGroup(DeviceId deviceId, GroupKey appCookie) {
+ return getStoredGroupEntry(deviceId, appCookie);
+ }
+
+ private StoredGroupEntry getStoredGroupEntry(DeviceId deviceId,
+ GroupKey appCookie) {
+ return getGroupStoreKeyMap().get(new GroupStoreKeyMapKey(deviceId,
+ appCookie));
+ }
+
+ @Override
+ public Group getGroup(DeviceId deviceId, GroupId groupId) {
+ return getStoredGroupEntry(deviceId, groupId);
+ }
+
+ private StoredGroupEntry getStoredGroupEntry(DeviceId deviceId,
+ GroupId groupId) {
+ return getGroupIdTable(deviceId).get(groupId);
+ }
+
+ private int getFreeGroupIdValue(DeviceId deviceId) {
+ int freeId = groupIdGen.incrementAndGet();
+
+ while (true) {
+ Group existing = getGroup(deviceId, new DefaultGroupId(freeId));
+ if (existing == null) {
+ existing = (
+ extraneousGroupEntriesById.get(deviceId) != null) ?
+ extraneousGroupEntriesById.get(deviceId).
+ get(new DefaultGroupId(freeId)) :
+ null;
+ }
+ if (existing != null) {
+ freeId = groupIdGen.incrementAndGet();
+ } else {
+ break;
+ }
+ }
+ log.debug("getFreeGroupIdValue: Next Free ID is {}", freeId);
+ return freeId;
+ }
+
+ /**
+ * Stores a new group entry using the information from group description.
+ *
+ * @param groupDesc group description to be used to create group entry
+ */
+ @Override
+ public void storeGroupDescription(GroupDescription groupDesc) {
+ log.debug("In storeGroupDescription");
+ // Check if a group is existing with the same key
+ if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+ log.warn("Group already exists with the same key {}",
+ groupDesc.appCookie());
+ return;
+ }
+
+ // Check if group to be created by a remote instance
+ if (mastershipService.getLocalRole(groupDesc.deviceId()) != MastershipRole.MASTER) {
+ log.debug("storeGroupDescription: Device {} local role is not MASTER",
+ groupDesc.deviceId());
+ if (mastershipService.getMasterFor(groupDesc.deviceId()) == null) {
+ log.error("No Master for device {}..."
+ + "Can not perform add group operation",
+ groupDesc.deviceId());
+ //TODO: Send Group operation failure event
+ return;
+ }
+ GroupStoreMessage groupOp = GroupStoreMessage.
+ createGroupAddRequestMsg(groupDesc.deviceId(),
+ groupDesc);
+
+ clusterCommunicator.unicast(groupOp,
+ GroupStoreMessageSubjects.REMOTE_GROUP_OP_REQUEST,
+ m -> kryoBuilder.build().serialize(m),
+ mastershipService.getMasterFor(groupDesc.deviceId())).whenComplete((result, error) -> {
+ if (error != null) {
+ log.warn("Failed to send request to master: {} to {}",
+ groupOp,
+ mastershipService.getMasterFor(groupDesc.deviceId()));
+ //TODO: Send Group operation failure event
+ } else {
+ log.debug("Sent Group operation request for device {} "
+ + "to remote MASTER {}",
+ groupDesc.deviceId(),
+ mastershipService.getMasterFor(groupDesc.deviceId()));
+ }
+ });
+ return;
+ }
+
+ log.debug("Store group for device {} is getting handled locally",
+ groupDesc.deviceId());
+ storeGroupDescriptionInternal(groupDesc);
+ }
+
+ private Group getMatchingExtraneousGroupbyId(DeviceId deviceId, Integer groupId) {
+ ConcurrentMap<GroupId, Group> extraneousMap =
+ extraneousGroupEntriesById.get(deviceId);
+ if (extraneousMap == null) {
+ return null;
+ }
+ return extraneousMap.get(new DefaultGroupId(groupId));
+ }
+
+ private Group getMatchingExtraneousGroupbyBuckets(DeviceId deviceId,
+ GroupBuckets buckets) {
+ ConcurrentMap<GroupId, Group> extraneousMap =
+ extraneousGroupEntriesById.get(deviceId);
+ if (extraneousMap == null) {
+ return null;
+ }
+
+ for (Group extraneousGroup:extraneousMap.values()) {
+ if (extraneousGroup.buckets().equals(buckets)) {
+ return extraneousGroup;
+ }
+ }
+ return null;
+ }
+
+ private void storeGroupDescriptionInternal(GroupDescription groupDesc) {
+ // Check if a group is existing with the same key
+ if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+ return;
+ }
+
+ if (deviceAuditStatus.get(groupDesc.deviceId()) == null) {
+ // Device group audit has not completed yet
+ // Add this group description to pending group key table
+ // Create a group entry object with Dummy Group ID
+ log.debug("storeGroupDescriptionInternal: Device {} AUDIT pending...Queuing Group ADD request",
+ groupDesc.deviceId());
+ StoredGroupEntry group = new DefaultGroup(dummyGroupId, groupDesc);
+ group.setState(GroupState.WAITING_AUDIT_COMPLETE);
+ EventuallyConsistentMap<GroupStoreKeyMapKey, StoredGroupEntry> pendingKeyTable =
+ getPendingGroupKeyTable();
+ pendingKeyTable.put(new GroupStoreKeyMapKey(groupDesc.deviceId(),
+ groupDesc.appCookie()),
+ group);
+ return;
+ }
+
+ Group matchingExtraneousGroup = null;
+ if (groupDesc.givenGroupId() != null) {
+ //Check if there is a extraneous group existing with the same Id
+ matchingExtraneousGroup = getMatchingExtraneousGroupbyId(
+ groupDesc.deviceId(), groupDesc.givenGroupId());
+ if (matchingExtraneousGroup != null) {
+ log.debug("storeGroupDescriptionInternal: Matching extraneous group found in Device {} for group id {}",
+ groupDesc.deviceId(),
+ groupDesc.givenGroupId());
+ //Check if the group buckets matches with user provided buckets
+ if (matchingExtraneousGroup.buckets().equals(groupDesc.buckets())) {
+ //Group is already existing with the same buckets and Id
+ // Create a group entry object
+ log.debug("storeGroupDescriptionInternal: Buckets also matching in Device {} for group id {}",
+ groupDesc.deviceId(),
+ groupDesc.givenGroupId());
+ StoredGroupEntry group = new DefaultGroup(
+ matchingExtraneousGroup.id(), groupDesc);
+ // Insert the newly created group entry into key and id maps
+ getGroupStoreKeyMap().
+ put(new GroupStoreKeyMapKey(groupDesc.deviceId(),
+ groupDesc.appCookie()), group);
+ // Ensure it also inserted into group id based table to
+ // avoid any chances of duplication in group id generation
+ getGroupIdTable(groupDesc.deviceId()).
+ put(matchingExtraneousGroup.id(), group);
+ addOrUpdateGroupEntry(matchingExtraneousGroup);
+ removeExtraneousGroupEntry(matchingExtraneousGroup);
+ return;
+ } else {
+ //Group buckets are not matching. Update group
+ //with user provided buckets.
+ //TODO
+ log.debug("storeGroupDescriptionInternal: Buckets are not matching in Device {} for group id {}",
+ groupDesc.deviceId(),
+ groupDesc.givenGroupId());
+ }
+ }
+ } else {
+ //Check if there is an extraneous group with user provided buckets
+ matchingExtraneousGroup = getMatchingExtraneousGroupbyBuckets(
+ groupDesc.deviceId(), groupDesc.buckets());
+ if (matchingExtraneousGroup != null) {
+ //Group is already existing with the same buckets.
+ //So reuse this group.
+ log.debug("storeGroupDescriptionInternal: Matching extraneous group found in Device {}",
+ groupDesc.deviceId());
+ //Create a group entry object
+ StoredGroupEntry group = new DefaultGroup(
+ matchingExtraneousGroup.id(), groupDesc);
+ // Insert the newly created group entry into key and id maps
+ getGroupStoreKeyMap().
+ put(new GroupStoreKeyMapKey(groupDesc.deviceId(),
+ groupDesc.appCookie()), group);
+ // Ensure it also inserted into group id based table to
+ // avoid any chances of duplication in group id generation
+ getGroupIdTable(groupDesc.deviceId()).
+ put(matchingExtraneousGroup.id(), group);
+ addOrUpdateGroupEntry(matchingExtraneousGroup);
+ removeExtraneousGroupEntry(matchingExtraneousGroup);
+ return;
+ } else {
+ //TODO: Check if there are any empty groups that can be used here
+ log.debug("storeGroupDescriptionInternal: No matching extraneous groups found in Device {}",
+ groupDesc.deviceId());
+ }
+ }
+
+ GroupId id = null;
+ if (groupDesc.givenGroupId() == null) {
+ // Get a new group identifier
+ id = new DefaultGroupId(getFreeGroupIdValue(groupDesc.deviceId()));
+ } else {
+ id = new DefaultGroupId(groupDesc.givenGroupId());
+ }
+ // Create a group entry object
+ StoredGroupEntry group = new DefaultGroup(id, groupDesc);
+ // Insert the newly created group entry into key and id maps
+ getGroupStoreKeyMap().
+ put(new GroupStoreKeyMapKey(groupDesc.deviceId(),
+ groupDesc.appCookie()), group);
+ // Ensure it also inserted into group id based table to
+ // avoid any chances of duplication in group id generation
+ getGroupIdTable(groupDesc.deviceId()).
+ put(id, group);
+ log.debug("storeGroupDescriptionInternal: Processing Group ADD request for Id {} in device {}",
+ id,
+ groupDesc.deviceId());
+ notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+ group));
+ }
+
+ /**
+ * Updates the existing group entry with the information
+ * from group description.
+ *
+ * @param deviceId the device ID
+ * @param oldAppCookie the current group key
+ * @param type update type
+ * @param newBuckets group buckets for updates
+ * @param newAppCookie optional new group key
+ */
+ @Override
+ public void updateGroupDescription(DeviceId deviceId,
+ GroupKey oldAppCookie,
+ UpdateType type,
+ GroupBuckets newBuckets,
+ GroupKey newAppCookie) {
+ // Check if group update to be done by a remote instance
+ if (mastershipService.getMasterFor(deviceId) != null &&
+ mastershipService.getLocalRole(deviceId) != MastershipRole.MASTER) {
+ log.debug("updateGroupDescription: Device {} local role is not MASTER",
+ deviceId);
+ if (mastershipService.getMasterFor(deviceId) == null) {
+ log.error("No Master for device {}..."
+ + "Can not perform update group operation",
+ deviceId);
+ //TODO: Send Group operation failure event
+ return;
+ }
+ GroupStoreMessage groupOp = GroupStoreMessage.
+ createGroupUpdateRequestMsg(deviceId,
+ oldAppCookie,
+ type,
+ newBuckets,
+ newAppCookie);
+
+ clusterCommunicator.unicast(groupOp,
+ GroupStoreMessageSubjects.REMOTE_GROUP_OP_REQUEST,
+ m -> kryoBuilder.build().serialize(m),
+ mastershipService.getMasterFor(deviceId)).whenComplete((result, error) -> {
+ if (error != null) {
+ log.warn("Failed to send request to master: {} to {}",
+ groupOp,
+ mastershipService.getMasterFor(deviceId), error);
+ }
+ //TODO: Send Group operation failure event
+ });
+ return;
+ }
+ log.debug("updateGroupDescription for device {} is getting handled locally",
+ deviceId);
+ updateGroupDescriptionInternal(deviceId,
+ oldAppCookie,
+ type,
+ newBuckets,
+ newAppCookie);
+ }
+
+ private void updateGroupDescriptionInternal(DeviceId deviceId,
+ GroupKey oldAppCookie,
+ UpdateType type,
+ GroupBuckets newBuckets,
+ GroupKey newAppCookie) {
+ // Check if a group is existing with the provided key
+ Group oldGroup = getGroup(deviceId, oldAppCookie);
+ if (oldGroup == null) {
+ log.warn("updateGroupDescriptionInternal: Group not found...strange");
+ return;
+ }
+
+ List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup,
+ type,
+ newBuckets);
+ if (newBucketList != null) {
+ // Create a new group object from the old group
+ GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+ GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie;
+ GroupDescription updatedGroupDesc = new DefaultGroupDescription(
+ oldGroup.deviceId(),
+ oldGroup.type(),
+ updatedBuckets,
+ newCookie,
+ oldGroup.givenGroupId(),
+ oldGroup.appId());
+ StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(),
+ updatedGroupDesc);
+ log.debug("updateGroupDescriptionInternal: group entry {} in device {} moving from {} to PENDING_UPDATE",
+ oldGroup.id(),
+ oldGroup.deviceId(),
+ oldGroup.state());
+ newGroup.setState(GroupState.PENDING_UPDATE);
+ newGroup.setLife(oldGroup.life());
+ newGroup.setPackets(oldGroup.packets());
+ newGroup.setBytes(oldGroup.bytes());
+ //Update the group entry in groupkey based map.
+ //Update to groupid based map will happen in the
+ //groupkey based map update listener
+ log.debug("updateGroupDescriptionInternal with type {}: Group updated with buckets",
+ type);
+ getGroupStoreKeyMap().
+ put(new GroupStoreKeyMapKey(newGroup.deviceId(),
+ newGroup.appCookie()), newGroup);
+ notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_REQUESTED, newGroup));
+ } else {
+ log.warn("updateGroupDescriptionInternal with type {}: No "
+ + "change in the buckets in update", type);
+ }
+ }
+
+ private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
+ UpdateType type,
+ GroupBuckets buckets) {
+ GroupBuckets oldBuckets = oldGroup.buckets();
+ List<GroupBucket> newBucketList = new ArrayList<GroupBucket>(
+ oldBuckets.buckets());
+ boolean groupDescUpdated = false;
+
+ if (type == UpdateType.ADD) {
+ // Check if the any of the new buckets are part of
+ // the old bucket list
+ for (GroupBucket addBucket:buckets.buckets()) {
+ if (!newBucketList.contains(addBucket)) {
+ newBucketList.add(addBucket);
+ groupDescUpdated = true;
+ }
+ }
+ } else if (type == UpdateType.REMOVE) {
+ // Check if the to be removed buckets are part of the
+ // old bucket list
+ for (GroupBucket removeBucket:buckets.buckets()) {
+ if (newBucketList.contains(removeBucket)) {
+ newBucketList.remove(removeBucket);
+ groupDescUpdated = true;
+ }
+ }
+ }
+
+ if (groupDescUpdated) {
+ return newBucketList;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Triggers deleting the existing group entry.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ */
+ @Override
+ public void deleteGroupDescription(DeviceId deviceId,
+ GroupKey appCookie) {
+ // Check if group to be deleted by a remote instance
+ if (mastershipService.
+ getLocalRole(deviceId) != MastershipRole.MASTER) {
+ log.debug("deleteGroupDescription: Device {} local role is not MASTER",
+ deviceId);
+ if (mastershipService.getMasterFor(deviceId) == null) {
+ log.error("No Master for device {}..."
+ + "Can not perform delete group operation",
+ deviceId);
+ //TODO: Send Group operation failure event
+ return;
+ }
+ GroupStoreMessage groupOp = GroupStoreMessage.
+ createGroupDeleteRequestMsg(deviceId,
+ appCookie);
+
+ clusterCommunicator.unicast(groupOp,
+ GroupStoreMessageSubjects.REMOTE_GROUP_OP_REQUEST,
+ m -> kryoBuilder.build().serialize(m),
+ mastershipService.getMasterFor(deviceId)).whenComplete((result, error) -> {
+ if (error != null) {
+ log.warn("Failed to send request to master: {} to {}",
+ groupOp,
+ mastershipService.getMasterFor(deviceId), error);
+ }
+ //TODO: Send Group operation failure event
+ });
+ return;
+ }
+ log.debug("deleteGroupDescription in device {} is getting handled locally",
+ deviceId);
+ deleteGroupDescriptionInternal(deviceId, appCookie);
+ }
+
+ private void deleteGroupDescriptionInternal(DeviceId deviceId,
+ GroupKey appCookie) {
+ // Check if a group is existing with the provided key
+ StoredGroupEntry existing = getStoredGroupEntry(deviceId, appCookie);
+ if (existing == null) {
+ return;
+ }
+
+ log.debug("deleteGroupDescriptionInternal: group entry {} in device {} moving from {} to PENDING_DELETE",
+ existing.id(),
+ existing.deviceId(),
+ existing.state());
+ synchronized (existing) {
+ existing.setState(GroupState.PENDING_DELETE);
+ }
+ log.debug("deleteGroupDescriptionInternal: in device {} issuing GROUP_REMOVE_REQUESTED",
+ deviceId);
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, existing));
+ }
+
+ /**
+ * Stores a new group entry, or updates an existing entry.
+ *
+ * @param group group entry
+ */
+ @Override
+ public void addOrUpdateGroupEntry(Group group) {
+ // check if this new entry is an update to an existing entry
+ StoredGroupEntry existing = getStoredGroupEntry(group.deviceId(),
+ group.id());
+ GroupEvent event = null;
+
+ if (existing != null) {
+ log.debug("addOrUpdateGroupEntry: updating group entry {} in device {}",
+ group.id(),
+ group.deviceId());
+ synchronized (existing) {
+ for (GroupBucket bucket:group.buckets().buckets()) {
+ Optional<GroupBucket> matchingBucket =
+ existing.buckets().buckets()
+ .stream()
+ .filter((existingBucket)->(existingBucket.equals(bucket)))
+ .findFirst();
+ if (matchingBucket.isPresent()) {
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setPackets(bucket.packets());
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setBytes(bucket.bytes());
+ } else {
+ log.warn("addOrUpdateGroupEntry: No matching "
+ + "buckets to update stats");
+ }
+ }
+ existing.setLife(group.life());
+ existing.setPackets(group.packets());
+ existing.setBytes(group.bytes());
+ if ((existing.state() == GroupState.PENDING_ADD) ||
+ (existing.state() == GroupState.PENDING_ADD_RETRY)) {
+ log.debug("addOrUpdateGroupEntry: group entry {} in device {} moving from {} to ADDED",
+ existing.id(),
+ existing.deviceId(),
+ existing.state());
+ existing.setState(GroupState.ADDED);
+ existing.setIsGroupStateAddedFirstTime(true);
+ event = new GroupEvent(Type.GROUP_ADDED, existing);
+ } else {
+ log.debug("addOrUpdateGroupEntry: group entry {} in device {} moving from {} to ADDED",
+ existing.id(),
+ existing.deviceId(),
+ GroupState.PENDING_UPDATE);
+ existing.setState(GroupState.ADDED);
+ existing.setIsGroupStateAddedFirstTime(false);
+ event = new GroupEvent(Type.GROUP_UPDATED, existing);
+ }
+ //Re-PUT map entries to trigger map update events
+ getGroupStoreKeyMap().
+ put(new GroupStoreKeyMapKey(existing.deviceId(),
+ existing.appCookie()), existing);
+ }
+ } else {
+ log.warn("addOrUpdateGroupEntry: Group update "
+ + "happening for a non-existing entry in the map");
+ }
+
+ if (event != null) {
+ notifyDelegate(event);
+ }
+ }
+
+ /**
+ * Removes the group entry from store.
+ *
+ * @param group group entry
+ */
+ @Override
+ public void removeGroupEntry(Group group) {
+ StoredGroupEntry existing = getStoredGroupEntry(group.deviceId(),
+ group.id());
+
+ if (existing != null) {
+ log.debug("removeGroupEntry: removing group entry {} in device {}",
+ group.id(),
+ group.deviceId());
+ //Removal from groupid based map will happen in the
+ //map update listener
+ getGroupStoreKeyMap().remove(new GroupStoreKeyMapKey(existing.deviceId(),
+ existing.appCookie()));
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, existing));
+ } else {
+ log.warn("removeGroupEntry for {} in device{} is "
+ + "not existing in our maps",
+ group.id(),
+ group.deviceId());
+ }
+ }
+
+ @Override
+ public void deviceInitialAuditCompleted(DeviceId deviceId,
+ boolean completed) {
+ synchronized (deviceAuditStatus) {
+ if (completed) {
+ log.debug("AUDIT completed for device {}",
+ deviceId);
+ deviceAuditStatus.put(deviceId, true);
+ // Execute all pending group requests
+ List<StoredGroupEntry> pendingGroupRequests =
+ getPendingGroupKeyTable().values()
+ .stream()
+ .filter(g-> g.deviceId().equals(deviceId))
+ .collect(Collectors.toList());
+ log.debug("processing pending group add requests for device {} and number of pending requests {}",
+ deviceId,
+ pendingGroupRequests.size());
+ for (Group group:pendingGroupRequests) {
+ GroupDescription tmp = new DefaultGroupDescription(
+ group.deviceId(),
+ group.type(),
+ group.buckets(),
+ group.appCookie(),
+ group.givenGroupId(),
+ group.appId());
+ storeGroupDescriptionInternal(tmp);
+ getPendingGroupKeyTable().
+ remove(new GroupStoreKeyMapKey(deviceId, group.appCookie()));
+ }
+ } else {
+ Boolean audited = deviceAuditStatus.get(deviceId);
+ if (audited != null && audited) {
+ log.debug("Clearing AUDIT status for device {}", deviceId);
+ deviceAuditStatus.put(deviceId, false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean deviceInitialAuditStatus(DeviceId deviceId) {
+ synchronized (deviceAuditStatus) {
+ Boolean audited = deviceAuditStatus.get(deviceId);
+ return audited != null && audited;
+ }
+ }
+
+ @Override
+ public void groupOperationFailed(DeviceId deviceId, GroupOperation operation) {
+
+ StoredGroupEntry existing = getStoredGroupEntry(deviceId,
+ operation.groupId());
+
+ if (existing == null) {
+ log.warn("No group entry with ID {} found ", operation.groupId());
+ return;
+ }
+
+ log.warn("groupOperationFailed: group operation {} failed"
+ + "for group {} in device {}",
+ operation.opType(),
+ existing.id(),
+ existing.deviceId());
+ switch (operation.opType()) {
+ case ADD:
+ if (existing.state() == GroupState.PENDING_ADD) {
+ //TODO: Need to add support for passing the group
+ //operation failure reason from group provider.
+ //If the error type is anything other than GROUP_EXISTS,
+ //then the GROUP_ADD_FAILED event should be raised even
+ //in PENDING_ADD_RETRY state also.
+ notifyDelegate(new GroupEvent(Type.GROUP_ADD_FAILED, existing));
+ log.warn("groupOperationFailed: cleaningup "
+ + "group {} from store in device {}....",
+ existing.id(),
+ existing.deviceId());
+ //Removal from groupid based map will happen in the
+ //map update listener
+ getGroupStoreKeyMap().remove(new GroupStoreKeyMapKey(existing.deviceId(),
+ existing.appCookie()));
+ }
+ break;
+ case MODIFY:
+ notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_FAILED, existing));
+ break;
+ case DELETE:
+ notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_FAILED, existing));
+ break;
+ default:
+ log.warn("Unknown group operation type {}", operation.opType());
+ }
+ }
+
+ @Override
+ public void addOrUpdateExtraneousGroupEntry(Group group) {
+ log.debug("add/update extraneous group entry {} in device {}",
+ group.id(),
+ group.deviceId());
+ ConcurrentMap<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(group.deviceId());
+ extraneousIdTable.put(group.id(), group);
+ // Don't remove the extraneous groups, instead re-use it when
+ // a group request comes with the same set of buckets
+ }
+
+ @Override
+ public void removeExtraneousGroupEntry(Group group) {
+ log.debug("remove extraneous group entry {} of device {} from store",
+ group.id(),
+ group.deviceId());
+ ConcurrentMap<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(group.deviceId());
+ extraneousIdTable.remove(group.id());
+ }
+
+ @Override
+ public Iterable<Group> getExtraneousGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(
+ getExtraneousGroupIdTable(deviceId).values());
+ }
+
+ /**
+ * Map handler to receive any events when the group key map is updated.
+ */
+ private class GroupStoreKeyMapListener implements
+ EventuallyConsistentMapListener<GroupStoreKeyMapKey, StoredGroupEntry> {
+
+ @Override
+ public void event(EventuallyConsistentMapEvent<GroupStoreKeyMapKey,
+ StoredGroupEntry> mapEvent) {
+ GroupEvent groupEvent = null;
+ GroupStoreKeyMapKey key = mapEvent.key();
+ StoredGroupEntry group = mapEvent.value();
+ if ((key == null) && (group == null)) {
+ log.error("GroupStoreKeyMapListener: Received "
+ + "event {} with null entry", mapEvent.type());
+ return;
+ } else if (group == null) {
+ group = getGroupIdTable(key.deviceId()).values()
+ .stream()
+ .filter((storedGroup) -> (storedGroup.appCookie().equals(key.appCookie)))
+ .findFirst().get();
+ if (group == null) {
+ log.error("GroupStoreKeyMapListener: Received "
+ + "event {} with null entry... can not process", mapEvent.type());
+ return;
+ }
+ }
+ log.trace("received groupid map event {} for id {} in device {}",
+ mapEvent.type(),
+ group.id(),
+ key.deviceId());
+ if (mapEvent.type() == EventuallyConsistentMapEvent.Type.PUT) {
+ // Update the group ID table
+ getGroupIdTable(group.deviceId()).put(group.id(), group);
+ if (mapEvent.value().state() == Group.GroupState.ADDED) {
+ if (mapEvent.value().isGroupStateAddedFirstTime()) {
+ groupEvent = new GroupEvent(Type.GROUP_ADDED,
+ mapEvent.value());
+ log.trace("Received first time GROUP_ADDED state update for id {} in device {}",
+ group.id(),
+ group.deviceId());
+ } else {
+ groupEvent = new GroupEvent(Type.GROUP_UPDATED,
+ mapEvent.value());
+ log.trace("Received following GROUP_ADDED state update for id {} in device {}",
+ group.id(),
+ group.deviceId());
+ }
+ }
+ } else if (mapEvent.type() == EventuallyConsistentMapEvent.Type.REMOVE) {
+ groupEvent = new GroupEvent(Type.GROUP_REMOVED, group);
+ // Remove the entry from the group ID table
+ getGroupIdTable(group.deviceId()).remove(group.id(), group);
+ }
+
+ if (groupEvent != null) {
+ notifyDelegate(groupEvent);
+ }
+ }
+ }
+
+ private void process(GroupStoreMessage groupOp) {
+ log.debug("Received remote group operation {} request for device {}",
+ groupOp.type(),
+ groupOp.deviceId());
+ if (!mastershipService.isLocalMaster(groupOp.deviceId())) {
+ log.warn("This node is not MASTER for device {}", groupOp.deviceId());
+ return;
+ }
+ if (groupOp.type() == GroupStoreMessage.Type.ADD) {
+ storeGroupDescriptionInternal(groupOp.groupDesc());
+ } else if (groupOp.type() == GroupStoreMessage.Type.UPDATE) {
+ updateGroupDescriptionInternal(groupOp.deviceId(),
+ groupOp.appCookie(),
+ groupOp.updateType(),
+ groupOp.updateBuckets(),
+ groupOp.newAppCookie());
+ } else if (groupOp.type() == GroupStoreMessage.Type.DELETE) {
+ deleteGroupDescriptionInternal(groupOp.deviceId(),
+ groupOp.appCookie());
+ }
+ }
+
+ /**
+ * Flattened map key to be used to store group entries.
+ */
+ protected static class GroupStoreMapKey {
+ private final DeviceId deviceId;
+
+ public GroupStoreMapKey(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GroupStoreMapKey)) {
+ return false;
+ }
+ GroupStoreMapKey that = (GroupStoreMapKey) o;
+ return this.deviceId.equals(that.deviceId);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + Objects.hash(this.deviceId);
+
+ return result;
+ }
+ }
+
+ protected static class GroupStoreKeyMapKey extends GroupStoreMapKey {
+ private final GroupKey appCookie;
+ public GroupStoreKeyMapKey(DeviceId deviceId,
+ GroupKey appCookie) {
+ super(deviceId);
+ this.appCookie = appCookie;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GroupStoreKeyMapKey)) {
+ return false;
+ }
+ GroupStoreKeyMapKey that = (GroupStoreKeyMapKey) o;
+ return (super.equals(that) &&
+ this.appCookie.equals(that.appCookie));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + super.hashCode() + Objects.hash(this.appCookie);
+
+ return result;
+ }
+ }
+
+ protected static class GroupStoreIdMapKey extends GroupStoreMapKey {
+ private final GroupId groupId;
+ public GroupStoreIdMapKey(DeviceId deviceId,
+ GroupId groupId) {
+ super(deviceId);
+ this.groupId = groupId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GroupStoreIdMapKey)) {
+ return false;
+ }
+ GroupStoreIdMapKey that = (GroupStoreIdMapKey) o;
+ return (super.equals(that) &&
+ this.groupId.equals(that.groupId));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + super.hashCode() + Objects.hash(this.groupId);
+
+ return result;
+ }
+ }
+
+ @Override
+ public void pushGroupMetrics(DeviceId deviceId,
+ Collection<Group> groupEntries) {
+ boolean deviceInitialAuditStatus =
+ deviceInitialAuditStatus(deviceId);
+ Set<Group> southboundGroupEntries =
+ Sets.newHashSet(groupEntries);
+ Set<StoredGroupEntry> storedGroupEntries =
+ Sets.newHashSet(getStoredGroups(deviceId));
+ Set<Group> extraneousStoredEntries =
+ Sets.newHashSet(getExtraneousGroups(deviceId));
+
+ log.trace("pushGroupMetrics: Displaying all ({}) southboundGroupEntries for device {}",
+ southboundGroupEntries.size(),
+ deviceId);
+ for (Iterator<Group> it = southboundGroupEntries.iterator(); it.hasNext();) {
+ Group group = it.next();
+ log.trace("Group {} in device {}", group, deviceId);
+ }
+
+ log.trace("Displaying all ({}) stored group entries for device {}",
+ storedGroupEntries.size(),
+ deviceId);
+ for (Iterator<StoredGroupEntry> it1 = storedGroupEntries.iterator();
+ it1.hasNext();) {
+ Group group = it1.next();
+ log.trace("Stored Group {} for device {}", group, deviceId);
+ }
+
+ for (Iterator<Group> it2 = southboundGroupEntries.iterator(); it2.hasNext();) {
+ Group group = it2.next();
+ if (storedGroupEntries.remove(group)) {
+ // we both have the group, let's update some info then.
+ log.trace("Group AUDIT: group {} exists in both planes for device {}",
+ group.id(), deviceId);
+ groupAdded(group);
+ it2.remove();
+ }
+ }
+ for (Group group : southboundGroupEntries) {
+ if (getGroup(group.deviceId(), group.id()) != null) {
+ // There is a group existing with the same id
+ // It is possible that group update is
+ // in progress while we got a stale info from switch
+ if (!storedGroupEntries.remove(getGroup(
+ group.deviceId(), group.id()))) {
+ log.warn("Group AUDIT: Inconsistent state:"
+ + "Group exists in ID based table while "
+ + "not present in key based table");
+ }
+ } else {
+ // there are groups in the switch that aren't in the store
+ log.debug("Group AUDIT: extraneous group {} exists in data plane for device {}",
+ group.id(), deviceId);
+ extraneousStoredEntries.remove(group);
+ extraneousGroup(group);
+ }
+ }
+ for (Group group : storedGroupEntries) {
+ // there are groups in the store that aren't in the switch
+ log.debug("Group AUDIT: group {} missing in data plane for device {}",
+ group.id(), deviceId);
+ groupMissing(group);
+ }
+ for (Group group : extraneousStoredEntries) {
+ // there are groups in the extraneous store that
+ // aren't in the switch
+ log.debug("Group AUDIT: clearing extransoeus group {} from store for device {}",
+ group.id(), deviceId);
+ removeExtraneousGroupEntry(group);
+ }
+
+ if (!deviceInitialAuditStatus) {
+ log.debug("Group AUDIT: Setting device {} initial AUDIT completed",
+ deviceId);
+ deviceInitialAuditCompleted(deviceId, true);
+ }
+ }
+
+ private void groupMissing(Group group) {
+ switch (group.state()) {
+ case PENDING_DELETE:
+ log.debug("Group {} delete confirmation from device {}",
+ group, group.deviceId());
+ removeGroupEntry(group);
+ break;
+ case ADDED:
+ case PENDING_ADD:
+ case PENDING_ADD_RETRY:
+ case PENDING_UPDATE:
+ log.debug("Group {} is in store but not on device {}",
+ group, group.deviceId());
+ StoredGroupEntry existing =
+ getStoredGroupEntry(group.deviceId(), group.id());
+ log.debug("groupMissing: group entry {} in device {} moving from {} to PENDING_ADD_RETRY",
+ existing.id(),
+ existing.deviceId(),
+ existing.state());
+ existing.setState(Group.GroupState.PENDING_ADD_RETRY);
+ //Re-PUT map entries to trigger map update events
+ getGroupStoreKeyMap().
+ put(new GroupStoreKeyMapKey(existing.deviceId(),
+ existing.appCookie()), existing);
+ notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+ group));
+ break;
+ default:
+ log.debug("Group {} has not been installed.", group);
+ break;
+ }
+ }
+
+ private void extraneousGroup(Group group) {
+ log.debug("Group {} is on device {} but not in store.",
+ group, group.deviceId());
+ addOrUpdateExtraneousGroupEntry(group);
+ }
+
+ private void groupAdded(Group group) {
+ log.trace("Group {} Added or Updated in device {}",
+ group, group.deviceId());
+ addOrUpdateGroupEntry(group);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessage.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessage.java
new file mode 100644
index 00000000..b82754b9
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessage.java
@@ -0,0 +1,184 @@
+/*
+ * 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.store.group.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupStore.UpdateType;
+
+/**
+ * Format of the Group store message that is used to
+ * communicate with the peer nodes in the cluster.
+ */
+public final class GroupStoreMessage {
+ private final DeviceId deviceId;
+ private final GroupKey appCookie;
+ private final GroupDescription groupDesc;
+ private final UpdateType updateType;
+ private final GroupBuckets updateBuckets;
+ private final GroupKey newAppCookie;
+ private final Type type;
+
+ /**
+ * Type of group store request.
+ */
+ public enum Type {
+ ADD,
+ UPDATE,
+ DELETE
+ }
+
+ private GroupStoreMessage(Type type,
+ DeviceId deviceId,
+ GroupKey appCookie,
+ GroupDescription groupDesc,
+ UpdateType updateType,
+ GroupBuckets updateBuckets,
+ GroupKey newAppCookie) {
+ this.type = type;
+ this.deviceId = deviceId;
+ this.appCookie = appCookie;
+ this.groupDesc = groupDesc;
+ this.updateType = updateType;
+ this.updateBuckets = updateBuckets;
+ this.newAppCookie = newAppCookie;
+ }
+
+ /**
+ * Creates a group store message for group ADD request.
+ *
+ * @param deviceId device identifier in which group to be added
+ * @param desc group creation parameters
+ * @return constructed group store message
+ */
+ public static GroupStoreMessage createGroupAddRequestMsg(DeviceId deviceId,
+ GroupDescription desc) {
+ return new GroupStoreMessage(Type.ADD,
+ deviceId,
+ null,
+ desc,
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Creates a group store message for group UPDATE request.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the current group key
+ * @param updateType update (add or delete) type
+ * @param updateBuckets group buckets for updates
+ * @param newAppCookie optional new group key
+ * @return constructed group store message
+ */
+ public static GroupStoreMessage createGroupUpdateRequestMsg(DeviceId deviceId,
+ GroupKey appCookie,
+ UpdateType updateType,
+ GroupBuckets updateBuckets,
+ GroupKey newAppCookie) {
+ return new GroupStoreMessage(Type.UPDATE,
+ deviceId,
+ appCookie,
+ null,
+ updateType,
+ updateBuckets,
+ newAppCookie);
+ }
+
+ /**
+ * Creates a group store message for group DELETE request.
+ *
+ * @param deviceId the device ID
+ * @param appCookie the group key
+ * @return constructed group store message
+ */
+ public static GroupStoreMessage createGroupDeleteRequestMsg(DeviceId deviceId,
+ GroupKey appCookie) {
+ return new GroupStoreMessage(Type.DELETE,
+ deviceId,
+ appCookie,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Returns the device identifier of this group request.
+ *
+ * @return device identifier
+ */
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ /**
+ * Returns the application cookie associated with this group request.
+ *
+ * @return application cookie
+ */
+ public GroupKey appCookie() {
+ return appCookie;
+ }
+
+ /**
+ * Returns the group create parameters associated with this group request.
+ *
+ * @return group create parameters
+ */
+ public GroupDescription groupDesc() {
+ return groupDesc;
+ }
+
+ /**
+ * Returns the group buckets to be updated as part of this group request.
+ *
+ * @return group buckets to be updated
+ */
+ public GroupBuckets updateBuckets() {
+ return updateBuckets;
+ }
+
+ /**
+ * Returns the update group operation type.
+ *
+ * @return update operation type
+ */
+ public UpdateType updateType() {
+ return updateType;
+ }
+
+ /**
+ * Returns the new application cookie associated with this group operation.
+ *
+ * @return new application cookie
+ */
+ public GroupKey newAppCookie() {
+ return newAppCookie;
+ }
+
+ /**
+ * Returns the type of this group operation.
+ *
+ * @return group message type
+ */
+ public Type type() {
+ return type;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessageSubjects.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessageSubjects.java
new file mode 100644
index 00000000..dbee22c7
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/GroupStoreMessageSubjects.java
@@ -0,0 +1,28 @@
+/*
+ * 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.store.group.impl;
+
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by DistributedGroupRuleStore peer-peer communication.
+ */
+public final class GroupStoreMessageSubjects {
+ private GroupStoreMessageSubjects() {}
+
+ public static final MessageSubject REMOTE_GROUP_OP_REQUEST
+ = new MessageSubject("peer-forward-group-op-req");
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java
new file mode 100644
index 00000000..35e3b251
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+/**
+ * Implementation of the group store.
+ */
+package org.onosproject.store.group.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/ECHostStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/ECHostStore.java
new file mode 100644
index 00000000..d8b9daca
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/ECHostStore.java
@@ -0,0 +1,267 @@
+package org.onosproject.store.host.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_UPDATED;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+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.onlab.util.KryoNamespace;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostStore;
+import org.onosproject.net.host.HostStoreDelegate;
+import org.onosproject.net.host.PortAddresses;
+import org.onosproject.net.host.HostEvent.Type;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.LogicalClockService;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+
+/**
+ * Manages the inventory of hosts using a {@code EventuallyConsistentMap}.
+ */
+@Component(immediate = true)
+@Service
+public class ECHostStore
+ extends AbstractStore<HostEvent, HostStoreDelegate>
+ implements HostStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LogicalClockService clockService;
+
+ // Hosts tracked by their location
+ private final SetMultimap<ConnectPoint, Host> locations =
+ Multimaps.synchronizedSetMultimap(
+ HashMultimap.<ConnectPoint, Host>create());
+
+ private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+ Multimaps.synchronizedSetMultimap(
+ HashMultimap.<ConnectPoint, PortAddresses>create());
+
+ private EventuallyConsistentMap<HostId, DefaultHost> hosts;
+
+ private EventuallyConsistentMapListener<HostId, DefaultHost> hostLocationTracker =
+ new HostLocationTracker();
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder hostSerializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API);
+
+ hosts = storageService.<HostId, DefaultHost>eventuallyConsistentMapBuilder()
+ .withName("onos-hosts")
+ .withSerializer(hostSerializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+
+ hosts.addListener(hostLocationTracker);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ hosts.removeListener(hostLocationTracker);
+ hosts.destroy();
+ locations.clear();
+ portAddresses.clear();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public HostEvent createOrUpdateHost(ProviderId providerId,
+ HostId hostId,
+ HostDescription hostDescription) {
+ DefaultHost currentHost = hosts.get(hostId);
+ if (currentHost == null) {
+ DefaultHost newhost = new DefaultHost(
+ providerId,
+ hostId,
+ hostDescription.hwAddress(),
+ hostDescription.vlan(),
+ hostDescription.location(),
+ ImmutableSet.copyOf(hostDescription.ipAddress()),
+ hostDescription.annotations());
+ hosts.put(hostId, newhost);
+ return new HostEvent(HOST_ADDED, newhost);
+ }
+ return updateHost(providerId, hostId, hostDescription, currentHost);
+ }
+
+ @Override
+ public HostEvent removeHost(HostId hostId) {
+ Host host = hosts.remove(hostId);
+ return host != null ? new HostEvent(HOST_REMOVED, host) : null;
+ }
+
+ @Override
+ public int getHostCount() {
+ return hosts.size();
+ }
+
+ @Override
+ public Iterable<Host> getHosts() {
+ return ImmutableSet.copyOf(hosts.values());
+ }
+
+ @Override
+ public Host getHost(HostId hostId) {
+ return hosts.get(hostId);
+ }
+
+ @Override
+ public Set<Host> getHosts(VlanId vlanId) {
+ return filter(hosts.values(), host -> Objects.equals(host.vlan(), vlanId));
+ }
+
+ @Override
+ public Set<Host> getHosts(MacAddress mac) {
+ return filter(hosts.values(), host -> Objects.equals(host.mac(), mac));
+ }
+
+ @Override
+ public Set<Host> getHosts(IpAddress ip) {
+ return filter(hosts.values(), host -> host.ipAddresses().contains(ip));
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+ return ImmutableSet.copyOf(locations.get(connectPoint));
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(DeviceId deviceId) {
+ return ImmutableMultimap.copyOf(locations)
+ .entries()
+ .stream()
+ .filter(entry -> entry.getKey().deviceId().equals(deviceId))
+ .map(entry -> entry.getValue())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public void updateAddressBindings(PortAddresses addresses) {
+ portAddresses.put(addresses.connectPoint(), addresses);
+ }
+
+ @Override
+ public void removeAddressBindings(PortAddresses addresses) {
+ portAddresses.remove(addresses.connectPoint(), addresses);
+ }
+
+ @Override
+ public void clearAddressBindings(ConnectPoint connectPoint) {
+ portAddresses.removeAll(connectPoint);
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindings() {
+ return ImmutableSet.copyOf(portAddresses.values());
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
+ synchronized (portAddresses) {
+ Set<PortAddresses> addresses = portAddresses.get(connectPoint);
+ return addresses == null ? Collections.emptySet() : ImmutableSet.copyOf(addresses);
+ }
+ }
+
+ private Set<Host> filter(Collection<DefaultHost> collection, Predicate<DefaultHost> predicate) {
+ return collection.stream().filter(predicate).collect(Collectors.toSet());
+ }
+
+ // checks for type of update to host, sends appropriate event
+ private HostEvent updateHost(ProviderId providerId,
+ HostId hostId,
+ HostDescription descr,
+ DefaultHost currentHost) {
+
+ final boolean hostMoved = !currentHost.location().equals(descr.location());
+ if (hostMoved ||
+ !currentHost.ipAddresses().containsAll(descr.ipAddress()) ||
+ !descr.annotations().keys().isEmpty()) {
+
+ Set<IpAddress> addresses = Sets.newHashSet(currentHost.ipAddresses());
+ addresses.addAll(descr.ipAddress());
+ Annotations annotations = merge((DefaultAnnotations) currentHost.annotations(),
+ descr.annotations());
+
+ DefaultHost updatedHost = new DefaultHost(providerId, currentHost.id(),
+ currentHost.mac(), currentHost.vlan(),
+ descr.location(),
+ addresses,
+ annotations);
+
+ // TODO: We need a way to detect conflicting changes and abort update.
+ hosts.put(hostId, updatedHost);
+ locations.remove(currentHost.location(), currentHost);
+ locations.put(updatedHost.location(), updatedHost);
+
+ HostEvent.Type eventType = hostMoved ? Type.HOST_MOVED : Type.HOST_UPDATED;
+ return new HostEvent(eventType, updatedHost);
+ }
+ return null;
+ }
+
+ private class HostLocationTracker implements EventuallyConsistentMapListener<HostId, DefaultHost> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<HostId, DefaultHost> event) {
+ DefaultHost host = checkNotNull(event.value());
+ if (event.type() == PUT) {
+ boolean isNew = locations.put(host.location(), host);
+ notifyDelegate(new HostEvent(isNew ? HOST_ADDED : HOST_UPDATED, host));
+ } else if (event.type() == REMOVE) {
+ if (locations.remove(host.location(), host)) {
+ notifyDelegate(new HostEvent(HOST_REMOVED, host));
+ }
+
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/host/impl/package-info.java
new file mode 100644
index 00000000..635b1131
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of the distributed host store using p2p synchronization protocol.
+ */
+package org.onosproject.store.host.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/LogicalTimestamp.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/LogicalTimestamp.java
new file mode 100644
index 00000000..5ae8b4f4
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/LogicalTimestamp.java
@@ -0,0 +1,68 @@
+package org.onosproject.store.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Objects;
+
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+
+/**
+ * Timestamp based on logical sequence value.
+ * <p>
+ * LogicalTimestamps are ordered by their sequence values.
+ */
+public class LogicalTimestamp implements Timestamp {
+
+ private final long value;
+
+ public LogicalTimestamp(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof LogicalTimestamp,
+ "Must be LogicalTimestamp", o);
+ LogicalTimestamp that = (LogicalTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.value, that.value)
+ .result();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof LogicalTimestamp)) {
+ return false;
+ }
+ LogicalTimestamp that = (LogicalTimestamp) obj;
+ return Objects.equals(this.value, that.value);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("value", value)
+ .toString();
+ }
+
+ /**
+ * Returns the sequence value.
+ *
+ * @return sequence value
+ */
+ public long value() {
+ return this.value;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/MastershipBasedTimestamp.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/MastershipBasedTimestamp.java
new file mode 100644
index 00000000..15b3c3c6
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/MastershipBasedTimestamp.java
@@ -0,0 +1,117 @@
+/*
+ * 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.store.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Objects;
+
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+
+/**
+ * A logical timestamp that derives its value from two things:
+ * <ul>
+ * <li> The current mastership term of the device.</li>
+ * <li> The value of the counter used for tracking topology events observed from
+ * the device during that current time of a device. </li>
+ * </ul>
+ */
+public final class MastershipBasedTimestamp implements Timestamp {
+
+ private final long termNumber;
+ private final long sequenceNumber;
+
+ /**
+ * Default version tuple.
+ *
+ * @param termNumber the mastership termNumber
+ * @param sequenceNumber the sequenceNumber number within the termNumber
+ */
+ public MastershipBasedTimestamp(long termNumber, long sequenceNumber) {
+ this.termNumber = termNumber;
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof MastershipBasedTimestamp,
+ "Must be MastershipBasedTimestamp", o);
+ MastershipBasedTimestamp that = (MastershipBasedTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.termNumber, that.termNumber)
+ .compare(this.sequenceNumber, that.sequenceNumber)
+ .result();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(termNumber, sequenceNumber);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof MastershipBasedTimestamp)) {
+ return false;
+ }
+ MastershipBasedTimestamp that = (MastershipBasedTimestamp) obj;
+ return Objects.equals(this.termNumber, that.termNumber) &&
+ Objects.equals(this.sequenceNumber, that.sequenceNumber);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("termNumber", termNumber)
+ .add("sequenceNumber", sequenceNumber)
+ .toString();
+ }
+
+ /**
+ * Returns the termNumber.
+ *
+ * @return termNumber
+ */
+ public long termNumber() {
+ return termNumber;
+ }
+
+ /**
+ * Returns the sequenceNumber number.
+ *
+ * @return sequenceNumber
+ */
+ public long sequenceNumber() {
+ return sequenceNumber;
+ }
+
+ // Default constructor for serialization
+
+ /**
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ protected MastershipBasedTimestamp() {
+ this.termNumber = -1;
+ this.sequenceNumber = -1;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/Timestamped.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/Timestamped.java
new file mode 100644
index 00000000..ae7267b8
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/Timestamped.java
@@ -0,0 +1,119 @@
+/*
+ * 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.store.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.store.Timestamp;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Wrapper class to store Timestamped value.
+ *
+ * @param <T> Timestamped value type
+ */
+public final class Timestamped<T> {
+
+ private final Timestamp timestamp;
+ private final T value;
+
+ /**
+ * Creates a time stamped value.
+ *
+ * @param value to be timestamp
+ * @param timestamp the timestamp
+ */
+ public Timestamped(T value, Timestamp timestamp) {
+ this.value = checkNotNull(value);
+ this.timestamp = checkNotNull(timestamp);
+ }
+
+ /**
+ * Returns the value.
+ *
+ * @return value
+ */
+ public T value() {
+ return value;
+ }
+
+ /**
+ * Returns the time stamp.
+ *
+ * @return time stamp
+ */
+ public Timestamp timestamp() {
+ return timestamp;
+ }
+
+ /**
+ * Tests if this timestamped value is newer than the other.
+ *
+ * @param other timestamped value
+ * @return true if this instance is newer.
+ */
+ public boolean isNewer(Timestamped<T> other) {
+ return isNewerThan(checkNotNull(other).timestamp());
+ }
+
+ /**
+ * Tests if this timestamp is newer than the specified timestamp.
+ *
+ * @param other timestamp to compare against
+ * @return true if this instance is newer
+ */
+ public boolean isNewerThan(Timestamp other) {
+ return timestamp.isNewerThan(other);
+ }
+
+ @Override
+ public int hashCode() {
+ return timestamp.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Timestamped)) {
+ return false;
+ }
+ @SuppressWarnings("unchecked")
+ Timestamped<T> that = (Timestamped<T>) obj;
+ return Objects.equals(this.timestamp, that.timestamp);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("timestamp", timestamp)
+ .add("value", value)
+ .toString();
+ }
+
+ // Default constructor for serialization
+ /**
+ * @deprecated in Cardinal Release
+ */
+ @Deprecated
+ private Timestamped() {
+ this.value = null;
+ this.timestamp = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/impl/package-info.java
new file mode 100644
index 00000000..03786fac
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Common facilities for use by various distributed stores.
+ */
+package org.onosproject.store.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java
new file mode 100644
index 00000000..fa3a0751
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java
@@ -0,0 +1,334 @@
+/*
+ * 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.store.intent.impl;
+
+import com.google.common.collect.ImmutableList;
+
+import org.apache.commons.lang.math.RandomUtils;
+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.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+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.PartitionService;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.service.MultiValuedTimestamp;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.PURGE_REQ;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of Intents in a distributed data store that uses optimistic
+ * replication and gossip based techniques.
+ */
+//FIXME we should listen for leadership changes. if the local instance has just
+// ... become a leader, scan the pending map and process those
+@Component(immediate = true, enabled = true)
+@Service
+public class GossipIntentStore
+ extends AbstractStore<IntentEvent, IntentStoreDelegate>
+ implements IntentStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // Map of intent key => current intent state
+ private EventuallyConsistentMap<Key, IntentData> currentMap;
+
+ // Map of intent key => pending intent operation
+ private EventuallyConsistentMap<Key, IntentData> pendingMap;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PartitionService partitionService;
+
+ private final AtomicLong sequenceNumber = new AtomicLong(0);
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder intentSerializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(IntentData.class)
+ .register(MultiValuedTimestamp.class)
+ .register(WallClockTimestamp.class);
+
+ currentMap = storageService.<Key, IntentData>eventuallyConsistentMapBuilder()
+ .withName("intent-current")
+ .withSerializer(intentSerializer)
+ .withTimestampProvider((key, intentData) ->
+ new MultiValuedTimestamp<>(intentData.version(),
+ sequenceNumber.getAndIncrement()))
+ .withPeerUpdateFunction((key, intentData) -> getPeerNodes(key, intentData))
+ .build();
+
+ pendingMap = storageService.<Key, IntentData>eventuallyConsistentMapBuilder()
+ .withName("intent-pending")
+ .withSerializer(intentSerializer)
+ .withTimestampProvider((key, intentData) -> new MultiValuedTimestamp<>(intentData.version(),
+ System.nanoTime()))
+ .withPeerUpdateFunction((key, intentData) -> getPeerNodes(key, intentData))
+ .build();
+
+ currentMap.addListener(new InternalCurrentListener());
+ pendingMap.addListener(new InternalPendingListener());
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ currentMap.destroy();
+ pendingMap.destroy();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public long getIntentCount() {
+ return currentMap.size();
+ }
+
+ @Override
+ public Iterable<Intent> getIntents() {
+ return currentMap.values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getIntentData(boolean localOnly, long olderThan) {
+ if (localOnly || olderThan > 0) {
+ long now = System.currentTimeMillis();
+ final WallClockTimestamp time = new WallClockTimestamp(now - olderThan);
+ return currentMap.values().stream()
+ .filter(data -> data.version().isOlderThan(time) &&
+ (!localOnly || isMaster(data.key())))
+ .collect(Collectors.toList());
+ }
+ return currentMap.values();
+ }
+
+ @Override
+ public IntentState getIntentState(Key intentKey) {
+ IntentData data = currentMap.get(intentKey);
+ if (data != null) {
+ return data.state();
+ }
+ return null;
+ }
+
+ @Override
+ public List<Intent> getInstallableIntents(Key intentKey) {
+ IntentData data = currentMap.get(intentKey);
+ if (data != null) {
+ return data.installables();
+ }
+ return null;
+ }
+
+
+
+ @Override
+ public void write(IntentData newData) {
+ checkNotNull(newData);
+
+ IntentData currentData = currentMap.get(newData.key());
+ if (IntentData.isUpdateAcceptable(currentData, newData)) {
+ // Only the master is modifying the current state. Therefore assume
+ // this always succeeds
+ if (newData.state() == PURGE_REQ) {
+ currentMap.remove(newData.key(), currentData);
+ } else {
+ currentMap.put(newData.key(), new IntentData(newData));
+ }
+
+ // if current.put succeeded
+ pendingMap.remove(newData.key(), newData);
+ }
+ }
+
+ private Collection<NodeId> getPeerNodes(Key key, IntentData data) {
+ NodeId master = partitionService.getLeader(key);
+ NodeId origin = (data != null) ? data.origin() : null;
+ if (master == null || origin == null) {
+ log.debug("Intent {} missing master and/or origin; master = {}, origin = {}",
+ key, master, origin);
+ }
+
+ NodeId me = clusterService.getLocalNode().id();
+ boolean isMaster = Objects.equals(master, me);
+ boolean isOrigin = Objects.equals(origin, me);
+ if (isMaster && isOrigin) {
+ return getRandomNode();
+ } else if (isMaster) {
+ return origin != null ? ImmutableList.of(origin) : getRandomNode();
+ } else if (isOrigin) {
+ return master != null ? ImmutableList.of(master) : getRandomNode();
+ } else {
+ log.warn("No master or origin for intent {}", key);
+ return master != null ? ImmutableList.of(master) : getRandomNode();
+ }
+ }
+
+ private List<NodeId> getRandomNode() {
+ NodeId me = clusterService.getLocalNode().id();
+ List<NodeId> nodes = clusterService.getNodes().stream()
+ .map(ControllerNode::id)
+ .filter(node -> !Objects.equals(node, me))
+ .collect(Collectors.toList());
+ if (nodes.size() == 0) {
+ return null;
+ }
+ return ImmutableList.of(nodes.get(RandomUtils.nextInt(nodes.size())));
+ }
+
+ @Override
+ public void batchWrite(Iterable<IntentData> updates) {
+ updates.forEach(this::write);
+ }
+
+ @Override
+ public Intent getIntent(Key key) {
+ IntentData data = currentMap.get(key);
+ if (data != null) {
+ return data.intent();
+ }
+ return null;
+ }
+
+ @Override
+ public IntentData getIntentData(Key key) {
+ IntentData current = currentMap.get(key);
+ if (current == null) {
+ return null;
+ }
+ return new IntentData(current);
+ }
+
+ @Override
+ public void addPending(IntentData data) {
+ checkNotNull(data);
+
+ if (data.version() == null) {
+ data.setVersion(new WallClockTimestamp());
+ }
+ data.setOrigin(clusterService.getLocalNode().id());
+ pendingMap.put(data.key(), new IntentData(data));
+ }
+
+ @Override
+ public boolean isMaster(Key intentKey) {
+ return partitionService.isMine(intentKey);
+ }
+
+ @Override
+ public Iterable<Intent> getPending() {
+ return pendingMap.values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData() {
+ return pendingMap.values();
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData(boolean localOnly, long olderThan) {
+ long now = System.currentTimeMillis();
+ final WallClockTimestamp time = new WallClockTimestamp(now - olderThan);
+ return pendingMap.values().stream()
+ .filter(data -> data.version().isOlderThan(time) &&
+ (!localOnly || isMaster(data.key())))
+ .collect(Collectors.toList());
+ }
+
+ private void notifyDelegateIfNotNull(IntentEvent event) {
+ if (event != null) {
+ notifyDelegate(event);
+ }
+ }
+
+ private final class InternalCurrentListener implements
+ EventuallyConsistentMapListener<Key, IntentData> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<Key, IntentData> event) {
+ IntentData intentData = event.value();
+
+ if (event.type() == EventuallyConsistentMapEvent.Type.PUT) {
+ // The current intents map has been updated. If we are master for
+ // this intent's partition, notify the Manager that it should
+ // emit notifications about updated tracked resources.
+ if (delegate != null && isMaster(event.value().intent().key())) {
+ delegate.onUpdate(new IntentData(intentData)); // copy for safety, likely unnecessary
+ }
+ notifyDelegateIfNotNull(IntentEvent.getEvent(intentData));
+ }
+ }
+ }
+
+ private final class InternalPendingListener implements
+ EventuallyConsistentMapListener<Key, IntentData> {
+ @Override
+ public void event(
+ EventuallyConsistentMapEvent<Key, IntentData> event) {
+ if (event.type() == EventuallyConsistentMapEvent.Type.PUT) {
+ // The pending intents map has been updated. If we are master for
+ // this intent's partition, notify the Manager that it should do
+ // some work.
+ if (isMaster(event.value().intent().key())) {
+ if (delegate != null) {
+ delegate.process(new IntentData(event.value()));
+ }
+ }
+
+ notifyDelegateIfNotNull(IntentEvent.getEvent(event.value()));
+ }
+ }
+ }
+
+}
+
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionId.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionId.java
new file mode 100644
index 00000000..885361f0
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionId.java
@@ -0,0 +1,68 @@
+/*
+ * 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.store.intent.impl;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Identifies a partition of the intent keyspace which will be assigned to and
+ * processed by a single ONOS instance at a time.
+ */
+public class PartitionId {
+ private final int id;
+
+ /**
+ * Creates a new partition ID.
+ *
+ * @param id the partition ID
+ */
+ PartitionId(int id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the integer ID value.
+ *
+ * @return ID value
+ */
+ public int value() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PartitionId)) {
+ return false;
+ }
+
+ PartitionId that = (PartitionId) o;
+ return Objects.equals(this.id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("partition ID", id)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionManager.java
new file mode 100644
index 00000000..09108d28
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/PartitionManager.java
@@ -0,0 +1,243 @@
+/*
+ * 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.store.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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.ListenerRegistry;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+/**
+ * Manages the assignment of intent keyspace partitions to instances.
+ */
+@Component(immediate = true)
+@Service
+public class PartitionManager implements PartitionService {
+
+ private static final Logger log = LoggerFactory.getLogger(PartitionManager.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ protected final AtomicBoolean rebalanceScheduled = new AtomicBoolean(false);
+
+ static final int NUM_PARTITIONS = 14;
+ private static final int BACKOFF_TIME = 2;
+ private static final int CHECK_PARTITION_BALANCE_PERIOD_SEC = 10;
+ private static final int RETRY_AFTER_DELAY_SEC = 5;
+
+ private static final String ELECTION_PREFIX = "intent-partition-";
+
+ private ListenerRegistry<PartitionEvent, PartitionEventListener> listenerRegistry;
+ private LeadershipEventListener leaderListener = new InternalLeadershipListener();
+ private ClusterEventListener clusterListener = new InternalClusterEventListener();
+
+ private ScheduledExecutorService executor = Executors
+ .newScheduledThreadPool(1);
+
+ @Activate
+ public void activate() {
+ leadershipService.addListener(leaderListener);
+ clusterService.addListener(clusterListener);
+
+ listenerRegistry = new ListenerRegistry<>();
+ eventDispatcher.addSink(PartitionEvent.class, listenerRegistry);
+
+ for (int i = 0; i < NUM_PARTITIONS; i++) {
+ leadershipService.runForLeadership(getPartitionPath(i));
+ }
+
+ executor.scheduleAtFixedRate(() -> scheduleRebalance(0), 0,
+ CHECK_PARTITION_BALANCE_PERIOD_SEC, TimeUnit.SECONDS);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ executor.shutdownNow();
+
+ eventDispatcher.removeSink(PartitionEvent.class);
+ leadershipService.removeListener(leaderListener);
+ clusterService.removeListener(clusterListener);
+ }
+
+ /**
+ * Sets the specified executor to be used for scheduling background tasks.
+ *
+ * @param executor scheduled executor service for background tasks
+ * @return this PartitionManager
+ */
+ public PartitionManager withScheduledExecutor(ScheduledExecutorService executor) {
+ this.executor = executor;
+ return this;
+ }
+
+ private String getPartitionPath(int i) {
+ return ELECTION_PREFIX + i;
+ }
+
+ private String getPartitionPath(PartitionId id) {
+ return getPartitionPath(id.value());
+ }
+
+ private PartitionId getPartitionForKey(Key intentKey) {
+ int partition = Math.abs((int) intentKey.hash()) % NUM_PARTITIONS;
+ //TODO investigate Guava consistent hash method
+ // ... does it add significant computational complexity? is it worth it?
+ //int partition = consistentHash(intentKey.hash(), NUM_PARTITIONS);
+ PartitionId id = new PartitionId(partition);
+ return id;
+ }
+
+ @Override
+ public boolean isMine(Key intentKey) {
+ return Objects.equals(leadershipService.getLeader(getPartitionPath(getPartitionForKey(intentKey))),
+ clusterService.getLocalNode().id());
+ }
+
+ @Override
+ public NodeId getLeader(Key intentKey) {
+ return leadershipService.getLeader(getPartitionPath(getPartitionForKey(intentKey)));
+ }
+
+ @Override
+ public void addListener(PartitionEventListener listener) {
+ listenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(PartitionEventListener listener) {
+ listenerRegistry.removeListener(listener);
+ }
+
+ protected void doRebalance() {
+ rebalanceScheduled.set(false);
+ try {
+ rebalance();
+ } catch (Exception e) {
+ log.warn("Exception caught during rebalance task. Will retry in " + RETRY_AFTER_DELAY_SEC + " seconds", e);
+ scheduleRebalance(RETRY_AFTER_DELAY_SEC);
+ }
+ }
+
+ /**
+ * Determine whether we have more than our fair share of partitions, and if
+ * so, relinquish leadership of some of them for a little while to let
+ * other instances take over.
+ */
+ private void rebalance() {
+ int activeNodes = (int) clusterService.getNodes()
+ .stream()
+ .filter(node -> ControllerNode.State.ACTIVE == clusterService.getState(node.id()))
+ .count();
+
+ int myShare = (int) Math.ceil((double) NUM_PARTITIONS / activeNodes);
+
+ List<Leadership> myPartitions = leadershipService.getLeaderBoard().values()
+ .stream()
+ .filter(l -> clusterService.getLocalNode().id().equals(l.leader()))
+ .filter(l -> l.topic().startsWith(ELECTION_PREFIX))
+ .collect(Collectors.toList());
+
+ int relinquish = myPartitions.size() - myShare;
+
+ if (relinquish <= 0) {
+ return;
+ }
+
+ for (int i = 0; i < relinquish; i++) {
+ String topic = myPartitions.get(i).topic();
+ leadershipService.withdraw(topic);
+
+ executor.schedule(() -> recontest(topic),
+ BACKOFF_TIME, TimeUnit.SECONDS);
+ }
+ }
+
+ private void scheduleRebalance(int afterDelaySec) {
+ if (rebalanceScheduled.compareAndSet(false, true)) {
+ executor.schedule(this::doRebalance, afterDelaySec, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Try and recontest for leadership of a partition.
+ *
+ * @param path topic name to recontest
+ */
+ private void recontest(String path) {
+ leadershipService.runForLeadership(path);
+ }
+
+ private final class InternalLeadershipListener implements LeadershipEventListener {
+
+ @Override
+ public void event(LeadershipEvent event) {
+ Leadership leadership = event.subject();
+
+ if (Objects.equals(leadership.leader(), clusterService.getLocalNode().id()) &&
+ leadership.topic().startsWith(ELECTION_PREFIX)) {
+
+ // See if we need to let some partitions go
+ scheduleRebalance(0);
+
+ eventDispatcher.post(new PartitionEvent(PartitionEvent.Type.LEADER_CHANGED,
+ leadership.topic()));
+ }
+ }
+ }
+
+ private final class InternalClusterEventListener implements
+ ClusterEventListener {
+
+ @Override
+ public void event(ClusterEvent event) {
+ scheduleRebalance(0);
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/impl/package-info.java
new file mode 100644
index 00000000..a8db8ff2
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/intent/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.
+ */
+
+/**
+ * Implementation of distributed intent store.
+ */
+package org.onosproject.store.intent.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java
new file mode 100644
index 00000000..4577086c
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java
@@ -0,0 +1,390 @@
+/*
+ * 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.store.link.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.onosproject.net.DefaultAnnotations.union;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.State.INACTIVE;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.Link.Type.INDIRECT;
+import static org.onosproject.net.LinkKey.linkKey;
+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.link.LinkEvent.Type.LINK_UPDATED;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
+import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+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.KryoNamespace;
+import org.onlab.util.SharedExecutors;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.AnnotationsUtil;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkStore;
+import org.onosproject.net.link.LinkStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.serializers.custom.DistributedStoreSerializers;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+
+/**
+ * Manages the inventory of links using a {@code EventuallyConsistentMap}.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ECLinkStore
+ extends AbstractStore<LinkEvent, LinkStoreDelegate>
+ implements LinkStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final Map<LinkKey, Link> links = Maps.newConcurrentMap();
+ private EventuallyConsistentMap<Provided<LinkKey>, LinkDescription> linkDescriptions;
+
+ private static final MessageSubject LINK_INJECT_MESSAGE = new MessageSubject("inject-link-request");
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceClockService deviceClockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ private EventuallyConsistentMapListener<Provided<LinkKey>, LinkDescription> linkTracker =
+ new InternalLinkTracker();
+
+ protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(DistributedStoreSerializers.STORE_COMMON)
+ .nextId(DistributedStoreSerializers.STORE_CUSTOM_BEGIN)
+ .register(Provided.class)
+ .build();
+ }
+ };
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(MastershipBasedTimestamp.class)
+ .register(Provided.class);
+
+ linkDescriptions = storageService.<Provided<LinkKey>, LinkDescription>eventuallyConsistentMapBuilder()
+ .withName("onos-link-descriptions")
+ .withSerializer(serializer)
+ .withTimestampProvider((k, v) -> {
+ try {
+ return v == null ? null : deviceClockService.getTimestamp(v.dst().deviceId());
+ } catch (IllegalStateException e) {
+ return null;
+ }
+ }).build();
+
+ clusterCommunicator.addSubscriber(LINK_INJECT_MESSAGE,
+ SERIALIZER::decode,
+ this::injectLink,
+ SERIALIZER::encode,
+ SharedExecutors.getPoolThreadExecutor());
+
+ linkDescriptions.addListener(linkTracker);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ linkDescriptions.removeListener(linkTracker);
+ linkDescriptions.destroy();
+ links.clear();
+ clusterCommunicator.removeSubscriber(LINK_INJECT_MESSAGE);
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public int getLinkCount() {
+ return links.size();
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return links.values();
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ return filter(links.values(), link -> deviceId.equals(link.src().deviceId()));
+ }
+
+ @Override
+ public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ return filter(links.values(), link -> deviceId.equals(link.dst().deviceId()));
+ }
+
+ @Override
+ public Link getLink(ConnectPoint src, ConnectPoint dst) {
+ return links.get(linkKey(src, dst));
+ }
+
+ @Override
+ public Set<Link> getEgressLinks(ConnectPoint src) {
+ return filter(links.values(), link -> src.equals(link.src()));
+ }
+
+ @Override
+ public Set<Link> getIngressLinks(ConnectPoint dst) {
+ return filter(links.values(), link -> dst.equals(link.dst()));
+ }
+
+ @Override
+ public LinkEvent createOrUpdateLink(ProviderId providerId,
+ LinkDescription linkDescription) {
+ final DeviceId dstDeviceId = linkDescription.dst().deviceId();
+ final NodeId dstNodeId = mastershipService.getMasterFor(dstDeviceId);
+
+ // Process link update only if we're the master of the destination node,
+ // otherwise signal the actual master.
+ if (clusterService.getLocalNode().id().equals(dstNodeId)) {
+ LinkKey linkKey = linkKey(linkDescription.src(), linkDescription.dst());
+ Provided<LinkKey> internalLinkKey = new Provided<>(linkKey, providerId);
+ linkDescriptions.compute(internalLinkKey, (k, v) -> createOrUpdateLinkInternal(v , linkDescription));
+ return refreshLinkCache(linkKey);
+ } else {
+ if (dstNodeId == null) {
+ return null;
+ }
+ return Futures.getUnchecked(clusterCommunicator.sendAndReceive(new Provided<>(linkDescription, providerId),
+ LINK_INJECT_MESSAGE,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ dstNodeId));
+ }
+ }
+
+ private LinkDescription createOrUpdateLinkInternal(LinkDescription current, LinkDescription updated) {
+ if (current != null) {
+ // we only allow transition from INDIRECT -> DIRECT
+ return new DefaultLinkDescription(
+ current.src(),
+ current.dst(),
+ current.type() == DIRECT ? DIRECT : updated.type(),
+ union(current.annotations(), updated.annotations()));
+ }
+ return updated;
+ }
+
+ private LinkEvent refreshLinkCache(LinkKey linkKey) {
+ AtomicReference<LinkEvent.Type> eventType = new AtomicReference<>();
+ Link link = links.compute(linkKey, (key, existingLink) -> {
+ Link newLink = composeLink(linkKey);
+ if (existingLink == null) {
+ eventType.set(LINK_ADDED);
+ return newLink;
+ } else if (existingLink.state() != newLink.state() ||
+ (existingLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+ !AnnotationsUtil.isEqual(existingLink.annotations(), newLink.annotations())) {
+ eventType.set(LINK_UPDATED);
+ return newLink;
+ } else {
+ return existingLink;
+ }
+ });
+ return eventType.get() != null ? new LinkEvent(eventType.get(), link) : null;
+ }
+
+ private Set<ProviderId> getAllProviders(LinkKey linkKey) {
+ return linkDescriptions.keySet()
+ .stream()
+ .filter(key -> key.key().equals(linkKey))
+ .map(key -> key.providerId())
+ .collect(Collectors.toSet());
+ }
+
+ private ProviderId getBaseProviderId(LinkKey linkKey) {
+ Set<ProviderId> allProviders = getAllProviders(linkKey);
+ if (allProviders.size() > 0) {
+ return allProviders.stream()
+ .filter(p -> !p.isAncillary())
+ .findFirst()
+ .orElse(Iterables.getFirst(allProviders, null));
+ }
+ return null;
+ }
+
+ private Link composeLink(LinkKey linkKey) {
+
+ ProviderId baseProviderId = checkNotNull(getBaseProviderId(linkKey));
+ LinkDescription base = linkDescriptions.get(new Provided<>(linkKey, baseProviderId));
+
+ ConnectPoint src = base.src();
+ ConnectPoint dst = base.dst();
+ Type type = base.type();
+ AtomicReference<DefaultAnnotations> annotations = new AtomicReference<>(DefaultAnnotations.builder().build());
+ annotations.set(merge(annotations.get(), base.annotations()));
+
+ getAllProviders(linkKey).stream()
+ .map(p -> new Provided<>(linkKey, p))
+ .forEach(key -> {
+ annotations.set(merge(annotations.get(),
+ linkDescriptions.get(key).annotations()));
+ });
+
+ boolean isDurable = Objects.equals(annotations.get().value(AnnotationKeys.DURABLE), "true");
+ return new DefaultLink(baseProviderId, src, dst, type, ACTIVE, isDurable, annotations.get());
+ }
+
+ // Updates, if necessary the specified link and returns the appropriate event.
+ // Guarded by linkDescs value (=locking each Link)
+ private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
+ // Note: INDIRECT -> DIRECT transition only
+ // so that BDDP discovered Link will not overwrite LDDP Link
+ if (oldLink.state() != newLink.state() ||
+ (oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+ !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
+
+ links.put(key, newLink);
+ return new LinkEvent(LINK_UPDATED, newLink);
+ }
+ return null;
+ }
+
+ @Override
+ public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
+ Link link = getLink(src, dst);
+ if (link == null) {
+ return null;
+ }
+
+ if (link.isDurable()) {
+ // FIXME: this will not sync link state!!!
+ return link.state() == INACTIVE ? null :
+ updateLink(linkKey(link.src(), link.dst()), link,
+ new DefaultLink(link.providerId(),
+ link.src(), link.dst(),
+ link.type(), INACTIVE,
+ link.isDurable(),
+ link.annotations()));
+ }
+ return removeLink(src, dst);
+ }
+
+ @Override
+ public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
+ final LinkKey linkKey = LinkKey.linkKey(src, dst);
+ ProviderId primaryProviderId = getBaseProviderId(linkKey);
+ // Stop if there is no base provider.
+ if (primaryProviderId == null) {
+ return null;
+ }
+ LinkDescription removedLinkDescription =
+ linkDescriptions.remove(new Provided<>(linkKey, primaryProviderId));
+ if (removedLinkDescription != null) {
+ return purgeLinkCache(linkKey);
+ }
+ return null;
+ }
+
+ private LinkEvent purgeLinkCache(LinkKey linkKey) {
+ Link removedLink = links.remove(linkKey);
+ if (removedLink != null) {
+ getAllProviders(linkKey).forEach(p -> linkDescriptions.remove(new Provided<>(linkKey, p)));
+ return new LinkEvent(LINK_REMOVED, removedLink);
+ }
+ return null;
+ }
+
+ private Set<Link> filter(Collection<Link> links, Predicate<Link> predicate) {
+ return links.stream().filter(predicate).collect(Collectors.toSet());
+ }
+
+ private LinkEvent injectLink(Provided<LinkDescription> linkInjectRequest) {
+ log.trace("Received request to inject link {}", linkInjectRequest);
+
+ ProviderId providerId = linkInjectRequest.providerId();
+ LinkDescription linkDescription = linkInjectRequest.key();
+
+ final DeviceId deviceId = linkDescription.dst().deviceId();
+ if (!deviceClockService.isTimestampAvailable(deviceId)) {
+ // workaround for ONOS-1208
+ log.warn("Not ready to accept update. Dropping {}", linkInjectRequest);
+ return null;
+ }
+ return createOrUpdateLink(providerId, linkDescription);
+ }
+
+ private class InternalLinkTracker implements EventuallyConsistentMapListener<Provided<LinkKey>, LinkDescription> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<Provided<LinkKey>, LinkDescription> event) {
+ if (event.type() == PUT) {
+ notifyDelegate(refreshLinkCache(event.key().key()));
+ } else if (event.type() == REMOVE) {
+ notifyDelegate(purgeLinkCache(event.key().key()));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStore.java
new file mode 100644
index 00000000..767ede54
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStore.java
@@ -0,0 +1,903 @@
+/*
+ * 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.store.link.impl;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.RandomUtils;
+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.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.AnnotationsUtil;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkStore;
+import org.onosproject.net.link.LinkStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.impl.Timestamped;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.serializers.custom.DistributedStoreSerializers;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+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 static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onlab.util.Tools.minPriority;
+import static org.onosproject.cluster.ControllerNodeToNodeId.toNodeId;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.onosproject.net.DefaultAnnotations.union;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.State.INACTIVE;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.Link.Type.INDIRECT;
+import static org.onosproject.net.LinkKey.linkKey;
+import static org.onosproject.net.link.LinkEvent.Type.*;
+import static org.onosproject.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of infrastructure links in distributed data store
+ * that uses optimistic replication and gossip based techniques.
+ */
+@Component(immediate = true, enabled = false)
+@Service
+public class GossipLinkStore
+ extends AbstractStore<LinkEvent, LinkStoreDelegate>
+ implements LinkStore {
+
+ // Timeout in milliseconds to process links on remote master node
+ private static final int REMOTE_MASTER_TIMEOUT = 1000;
+
+ private final Logger log = getLogger(getClass());
+
+ // Link inventory
+ private final ConcurrentMap<LinkKey, Map<ProviderId, Timestamped<LinkDescription>>> linkDescs =
+ new ConcurrentHashMap<>();
+
+ // Link instance cache
+ private final ConcurrentMap<LinkKey, Link> links = new ConcurrentHashMap<>();
+
+ // Egress and ingress link sets
+ private final SetMultimap<DeviceId, LinkKey> srcLinks = createSynchronizedHashMultiMap();
+ private final SetMultimap<DeviceId, LinkKey> dstLinks = createSynchronizedHashMultiMap();
+
+ // Remove links
+ private final Map<LinkKey, Timestamp> removedLinks = new ConcurrentHashMap<>();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceClockService deviceClockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(DistributedStoreSerializers.STORE_COMMON)
+ .nextId(DistributedStoreSerializers.STORE_CUSTOM_BEGIN)
+ .register(InternalLinkEvent.class)
+ .register(InternalLinkRemovedEvent.class)
+ .register(LinkAntiEntropyAdvertisement.class)
+ .register(LinkFragmentId.class)
+ .register(LinkInjectedEvent.class)
+ .build();
+ }
+ };
+
+ private ExecutorService executor;
+
+ private ScheduledExecutorService backgroundExecutors;
+
+ @Activate
+ public void activate() {
+
+ executor = Executors.newCachedThreadPool(groupedThreads("onos/link", "fg-%d"));
+
+ backgroundExecutors =
+ newSingleThreadScheduledExecutor(minPriority(groupedThreads("onos/link", "bg-%d")));
+
+ clusterCommunicator.addSubscriber(
+ GossipLinkStoreMessageSubjects.LINK_UPDATE,
+ new InternalLinkEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipLinkStoreMessageSubjects.LINK_REMOVED,
+ new InternalLinkRemovedEventListener(), executor);
+ clusterCommunicator.addSubscriber(
+ GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT,
+ new InternalLinkAntiEntropyAdvertisementListener(), backgroundExecutors);
+ clusterCommunicator.addSubscriber(
+ GossipLinkStoreMessageSubjects.LINK_INJECTED,
+ new LinkInjectedEventListener(), executor);
+
+ long initialDelaySec = 5;
+ long periodSec = 5;
+ // start anti-entropy thread
+ backgroundExecutors.scheduleAtFixedRate(new SendAdvertisementTask(),
+ initialDelaySec, periodSec, TimeUnit.SECONDS);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+
+ executor.shutdownNow();
+
+ backgroundExecutors.shutdownNow();
+ try {
+ if (!backgroundExecutors.awaitTermination(5, TimeUnit.SECONDS)) {
+ log.error("Timeout during executor shutdown");
+ }
+ } catch (InterruptedException e) {
+ log.error("Error during executor shutdown", e);
+ }
+
+ linkDescs.clear();
+ links.clear();
+ srcLinks.clear();
+ dstLinks.clear();
+ log.info("Stopped");
+ }
+
+ @Override
+ public int getLinkCount() {
+ return links.size();
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ // lock for iteration
+ synchronized (srcLinks) {
+ return FluentIterable.from(srcLinks.get(deviceId))
+ .transform(lookupLink())
+ .filter(notNull())
+ .toSet();
+ }
+ }
+
+ @Override
+ public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ // lock for iteration
+ synchronized (dstLinks) {
+ return FluentIterable.from(dstLinks.get(deviceId))
+ .transform(lookupLink())
+ .filter(notNull())
+ .toSet();
+ }
+ }
+
+ @Override
+ public Link getLink(ConnectPoint src, ConnectPoint dst) {
+ return links.get(linkKey(src, dst));
+ }
+
+ @Override
+ public Set<Link> getEgressLinks(ConnectPoint src) {
+ Set<Link> egress = new HashSet<>();
+ //
+ // Change `srcLinks` to ConcurrentMap<DeviceId, (Concurrent)Set>
+ // to remove this synchronized block, if we hit performance issue.
+ // SetMultiMap#get returns wrapped collection to provide modifiable-view.
+ // And the wrapped collection is not concurrent access safe.
+ //
+ // Our use case here does not require returned collection to be modifiable,
+ // so the wrapped collection forces us to lock the whole multiset,
+ // for benefit we don't need.
+ //
+ // Same applies to `dstLinks`
+ synchronized (srcLinks) {
+ for (LinkKey linkKey : srcLinks.get(src.deviceId())) {
+ if (linkKey.src().equals(src)) {
+ Link link = links.get(linkKey);
+ if (link != null) {
+ egress.add(link);
+ } else {
+ log.debug("Egress link for {} was null, skipped", linkKey);
+ }
+ }
+ }
+ }
+ return egress;
+ }
+
+ @Override
+ public Set<Link> getIngressLinks(ConnectPoint dst) {
+ Set<Link> ingress = new HashSet<>();
+ synchronized (dstLinks) {
+ for (LinkKey linkKey : dstLinks.get(dst.deviceId())) {
+ if (linkKey.dst().equals(dst)) {
+ Link link = links.get(linkKey);
+ if (link != null) {
+ ingress.add(link);
+ } else {
+ log.debug("Ingress link for {} was null, skipped", linkKey);
+ }
+ }
+ }
+ }
+ return ingress;
+ }
+
+ @Override
+ public LinkEvent createOrUpdateLink(ProviderId providerId,
+ LinkDescription linkDescription) {
+
+ final DeviceId dstDeviceId = linkDescription.dst().deviceId();
+ final NodeId localNode = clusterService.getLocalNode().id();
+ final NodeId dstNode = mastershipService.getMasterFor(dstDeviceId);
+
+ // Process link update only if we're the master of the destination node,
+ // otherwise signal the actual master.
+ LinkEvent linkEvent = null;
+ if (localNode.equals(dstNode)) {
+
+ Timestamp newTimestamp = deviceClockService.getTimestamp(dstDeviceId);
+
+ final Timestamped<LinkDescription> deltaDesc = new Timestamped<>(linkDescription, newTimestamp);
+
+ LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
+ final Timestamped<LinkDescription> mergedDesc;
+ Map<ProviderId, Timestamped<LinkDescription>> map = getOrCreateLinkDescriptions(key);
+
+ synchronized (map) {
+ linkEvent = createOrUpdateLinkInternal(providerId, deltaDesc);
+ mergedDesc = map.get(providerId);
+ }
+
+ if (linkEvent != null) {
+ log.debug("Notifying peers of a link update topology event from providerId: "
+ + "{} between src: {} and dst: {}",
+ providerId, linkDescription.src(), linkDescription.dst());
+ notifyPeers(new InternalLinkEvent(providerId, mergedDesc));
+ }
+
+ } else {
+ // FIXME Temporary hack for NPE (ONOS-1171).
+ // Proper fix is to implement forwarding to master on ConfigProvider
+ // redo ONOS-490
+ if (dstNode == null) {
+ // silently ignore
+ return null;
+ }
+
+
+ LinkInjectedEvent linkInjectedEvent = new LinkInjectedEvent(providerId, linkDescription);
+
+ // TODO check unicast return value
+ clusterCommunicator.unicast(linkInjectedEvent,
+ GossipLinkStoreMessageSubjects.LINK_INJECTED,
+ SERIALIZER::encode,
+ dstNode);
+ }
+
+ return linkEvent;
+ }
+
+ @Override
+ public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
+ Link link = getLink(src, dst);
+ if (link == null) {
+ return null;
+ }
+
+ if (link.isDurable()) {
+ // FIXME: this is not the right thing to call for the gossip store; will not sync link state!!!
+ return link.state() == INACTIVE ? null :
+ updateLink(linkKey(link.src(), link.dst()), link,
+ new DefaultLink(link.providerId(),
+ link.src(), link.dst(),
+ link.type(), INACTIVE,
+ link.isDurable(),
+ link.annotations()));
+ }
+ return removeLink(src, dst);
+ }
+
+ private LinkEvent createOrUpdateLinkInternal(
+ ProviderId providerId,
+ Timestamped<LinkDescription> linkDescription) {
+
+ final LinkKey key = linkKey(linkDescription.value().src(),
+ linkDescription.value().dst());
+ Map<ProviderId, Timestamped<LinkDescription>> descs = getOrCreateLinkDescriptions(key);
+
+ synchronized (descs) {
+ // if the link was previously removed, we should proceed if and
+ // only if this request is more recent.
+ Timestamp linkRemovedTimestamp = removedLinks.get(key);
+ if (linkRemovedTimestamp != null) {
+ if (linkDescription.isNewerThan(linkRemovedTimestamp)) {
+ removedLinks.remove(key);
+ } else {
+ log.trace("Link {} was already removed ignoring.", key);
+ return null;
+ }
+ }
+
+ final Link oldLink = links.get(key);
+ // update description
+ createOrUpdateLinkDescription(descs, providerId, linkDescription);
+ final Link newLink = composeLink(descs);
+ if (oldLink == null) {
+ return createLink(key, newLink);
+ }
+ return updateLink(key, oldLink, newLink);
+ }
+ }
+
+ // Guarded by linkDescs value (=locking each Link)
+ private Timestamped<LinkDescription> createOrUpdateLinkDescription(
+ Map<ProviderId, Timestamped<LinkDescription>> descs,
+ ProviderId providerId,
+ Timestamped<LinkDescription> linkDescription) {
+
+ // merge existing annotations
+ Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId);
+ if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) {
+ log.trace("local info is more up-to-date, ignoring {}.", linkDescription);
+ return null;
+ }
+ Timestamped<LinkDescription> newLinkDescription = linkDescription;
+ if (existingLinkDescription != null) {
+ // we only allow transition from INDIRECT -> DIRECT
+ final Type newType;
+ if (existingLinkDescription.value().type() == DIRECT) {
+ newType = DIRECT;
+ } else {
+ newType = linkDescription.value().type();
+ }
+ SparseAnnotations merged = union(existingLinkDescription.value().annotations(),
+ linkDescription.value().annotations());
+ newLinkDescription = new Timestamped<>(
+ new DefaultLinkDescription(
+ linkDescription.value().src(),
+ linkDescription.value().dst(),
+ newType, merged),
+ linkDescription.timestamp());
+ }
+ return descs.put(providerId, newLinkDescription);
+ }
+
+ // Creates and stores the link and returns the appropriate event.
+ // Guarded by linkDescs value (=locking each Link)
+ private LinkEvent createLink(LinkKey key, Link newLink) {
+ links.put(key, newLink);
+ srcLinks.put(newLink.src().deviceId(), key);
+ dstLinks.put(newLink.dst().deviceId(), key);
+ return new LinkEvent(LINK_ADDED, newLink);
+ }
+
+ // Updates, if necessary the specified link and returns the appropriate event.
+ // Guarded by linkDescs value (=locking each Link)
+ private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
+ // Note: INDIRECT -> DIRECT transition only
+ // so that BDDP discovered Link will not overwrite LDDP Link
+ if (oldLink.state() != newLink.state() ||
+ (oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+ !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
+
+ links.put(key, newLink);
+ // strictly speaking following can be omitted
+ srcLinks.put(oldLink.src().deviceId(), key);
+ dstLinks.put(oldLink.dst().deviceId(), key);
+ return new LinkEvent(LINK_UPDATED, newLink);
+ }
+ return null;
+ }
+
+ @Override
+ public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
+ final LinkKey key = linkKey(src, dst);
+
+ DeviceId dstDeviceId = dst.deviceId();
+ Timestamp timestamp = null;
+ try {
+ timestamp = deviceClockService.getTimestamp(dstDeviceId);
+ } catch (IllegalStateException e) {
+ log.debug("Failed to remove link {}, was not the master", key);
+ // there are times when this is called before mastership
+ // handoff correctly completes.
+ return null;
+ }
+
+ LinkEvent event = removeLinkInternal(key, timestamp);
+
+ if (event != null) {
+ log.debug("Notifying peers of a link removed topology event for a link "
+ + "between src: {} and dst: {}", src, dst);
+ notifyPeers(new InternalLinkRemovedEvent(key, timestamp));
+ }
+ return event;
+ }
+
+ private static Timestamped<LinkDescription> getPrimaryDescription(
+ Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
+
+ synchronized (linkDescriptions) {
+ for (Entry<ProviderId, Timestamped<LinkDescription>>
+ e : linkDescriptions.entrySet()) {
+
+ if (!e.getKey().isAncillary()) {
+ return e.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+
+ // TODO: consider slicing out as Timestamp utils
+ /**
+ * Checks is timestamp is more recent than timestamped object.
+ *
+ * @param timestamp to check if this is more recent then other
+ * @param timestamped object to be tested against
+ * @return true if {@code timestamp} is more recent than {@code timestamped}
+ * or {@code timestamped is null}
+ */
+ private static boolean isMoreRecent(Timestamp timestamp, Timestamped<?> timestamped) {
+ checkNotNull(timestamp);
+ if (timestamped == null) {
+ return true;
+ }
+ return timestamp.compareTo(timestamped.timestamp()) > 0;
+ }
+
+ private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) {
+ Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions
+ = getOrCreateLinkDescriptions(key);
+
+ synchronized (linkDescriptions) {
+ if (linkDescriptions.isEmpty()) {
+ // never seen such link before. keeping timestamp for record
+ removedLinks.put(key, timestamp);
+ return null;
+ }
+ // accept removal request if given timestamp is newer than
+ // the latest Timestamp from Primary provider
+ Timestamped<LinkDescription> prim = getPrimaryDescription(linkDescriptions);
+ if (!isMoreRecent(timestamp, prim)) {
+ // outdated remove request, ignore
+ return null;
+ }
+ removedLinks.put(key, timestamp);
+ Link link = links.remove(key);
+ linkDescriptions.clear();
+ if (link != null) {
+ srcLinks.remove(link.src().deviceId(), key);
+ dstLinks.remove(link.dst().deviceId(), key);
+ return new LinkEvent(LINK_REMOVED, link);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Creates concurrent readable, synchronized HashMultimap.
+ *
+ * @return SetMultimap
+ */
+ private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
+ return synchronizedSetMultimap(
+ Multimaps.newSetMultimap(new ConcurrentHashMap<K, Collection<V>>(),
+ () -> Sets.newConcurrentHashSet()));
+ }
+
+ /**
+ * @return primary ProviderID, or randomly chosen one if none exists
+ */
+ private static ProviderId pickBaseProviderId(
+ Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
+
+ ProviderId fallBackPrimary = null;
+ for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) {
+ if (!e.getKey().isAncillary()) {
+ // found primary
+ return e.getKey();
+ } else if (fallBackPrimary == null) {
+ // pick randomly as a fallback in case there is no primary
+ fallBackPrimary = e.getKey();
+ }
+ }
+ return fallBackPrimary;
+ }
+
+ // Guarded by linkDescs value (=locking each Link)
+ private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) {
+ ProviderId baseProviderId = pickBaseProviderId(descs);
+ Timestamped<LinkDescription> base = descs.get(baseProviderId);
+
+ ConnectPoint src = base.value().src();
+ ConnectPoint dst = base.value().dst();
+ Type type = base.value().type();
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ annotations = merge(annotations, base.value().annotations());
+
+ for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) {
+ if (baseProviderId.equals(e.getKey())) {
+ continue;
+ }
+
+ // Note: In the long run we should keep track of Description timestamp
+ // and only merge conflicting keys when timestamp is newer
+ // Currently assuming there will never be a key conflict between
+ // providers
+
+ // annotation merging. not so efficient, should revisit later
+ annotations = merge(annotations, e.getValue().value().annotations());
+ }
+
+ boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
+ return new DefaultLink(baseProviderId, src, dst, type, ACTIVE, isDurable, annotations);
+ }
+
+ private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
+ Map<ProviderId, Timestamped<LinkDescription>> r;
+ r = linkDescs.get(key);
+ if (r != null) {
+ return r;
+ }
+ r = new HashMap<>();
+ final Map<ProviderId, Timestamped<LinkDescription>> concurrentlyAdded;
+ concurrentlyAdded = linkDescs.putIfAbsent(key, r);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ } else {
+ return r;
+ }
+ }
+
+ private final Function<LinkKey, Link> lookupLink = new LookupLink();
+
+ /**
+ * Returns a Function to lookup Link instance using LinkKey from cache.
+ *
+ * @return lookup link function
+ */
+ private Function<LinkKey, Link> lookupLink() {
+ return lookupLink;
+ }
+
+ private final class LookupLink implements Function<LinkKey, Link> {
+ @Override
+ public Link apply(LinkKey input) {
+ if (input == null) {
+ return null;
+ } else {
+ return links.get(input);
+ }
+ }
+ }
+
+ private void notifyDelegateIfNotNull(LinkEvent event) {
+ if (event != null) {
+ notifyDelegate(event);
+ }
+ }
+
+ private void broadcastMessage(MessageSubject subject, Object event) {
+ clusterCommunicator.broadcast(event, subject, SERIALIZER::encode);
+ }
+
+ private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) throws IOException {
+ clusterCommunicator.unicast(event, subject, SERIALIZER::encode, recipient);
+ }
+
+ private void notifyPeers(InternalLinkEvent event) {
+ broadcastMessage(GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
+ }
+
+ private void notifyPeers(InternalLinkRemovedEvent event) {
+ broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
+ }
+
+ // notify peer, silently ignoring error
+ private void notifyPeer(NodeId peer, InternalLinkEvent event) {
+ try {
+ unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
+ } catch (IOException e) {
+ log.debug("Failed to notify peer {} with message {}", peer, event);
+ }
+ }
+
+ // notify peer, silently ignoring error
+ private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) {
+ try {
+ unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
+ } catch (IOException e) {
+ log.debug("Failed to notify peer {} with message {}", peer, event);
+ }
+ }
+
+ private final class SendAdvertisementTask implements Runnable {
+
+ @Override
+ public void run() {
+ if (Thread.currentThread().isInterrupted()) {
+ log.debug("Interrupted, quitting");
+ return;
+ }
+
+ try {
+ final NodeId self = clusterService.getLocalNode().id();
+ Set<ControllerNode> nodes = clusterService.getNodes();
+
+ ImmutableList<NodeId> nodeIds = FluentIterable.from(nodes)
+ .transform(toNodeId())
+ .toList();
+
+ if (nodeIds.size() == 1 && nodeIds.get(0).equals(self)) {
+ log.trace("No other peers in the cluster.");
+ return;
+ }
+
+ NodeId peer;
+ do {
+ int idx = RandomUtils.nextInt(0, nodeIds.size());
+ peer = nodeIds.get(idx);
+ } while (peer.equals(self));
+
+ LinkAntiEntropyAdvertisement ad = createAdvertisement();
+
+ if (Thread.currentThread().isInterrupted()) {
+ log.debug("Interrupted, quitting");
+ return;
+ }
+
+ try {
+ unicastMessage(peer, LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
+ } catch (IOException e) {
+ log.debug("Failed to send anti-entropy advertisement to {}", peer);
+ return;
+ }
+ } catch (Exception e) {
+ // catch all Exception to avoid Scheduled task being suppressed.
+ log.error("Exception thrown while sending advertisement", e);
+ }
+ }
+ }
+
+ private LinkAntiEntropyAdvertisement createAdvertisement() {
+ final NodeId self = clusterService.getLocalNode().id();
+
+ Map<LinkFragmentId, Timestamp> linkTimestamps = new HashMap<>(linkDescs.size());
+ Map<LinkKey, Timestamp> linkTombstones = new HashMap<>(removedLinks.size());
+
+ linkDescs.forEach((linkKey, linkDesc) -> {
+ synchronized (linkDesc) {
+ for (Map.Entry<ProviderId, Timestamped<LinkDescription>> e : linkDesc.entrySet()) {
+ linkTimestamps.put(new LinkFragmentId(linkKey, e.getKey()), e.getValue().timestamp());
+ }
+ }
+ });
+
+ linkTombstones.putAll(removedLinks);
+
+ return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones);
+ }
+
+ private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement ad) {
+
+ final NodeId sender = ad.sender();
+ boolean localOutdated = false;
+
+ for (Entry<LinkKey, Map<ProviderId, Timestamped<LinkDescription>>>
+ l : linkDescs.entrySet()) {
+
+ final LinkKey key = l.getKey();
+ final Map<ProviderId, Timestamped<LinkDescription>> link = l.getValue();
+ synchronized (link) {
+ Timestamp localLatest = removedLinks.get(key);
+
+ for (Entry<ProviderId, Timestamped<LinkDescription>> p : link.entrySet()) {
+ final ProviderId providerId = p.getKey();
+ final Timestamped<LinkDescription> pDesc = p.getValue();
+
+ final LinkFragmentId fragId = new LinkFragmentId(key, providerId);
+ // remote
+ Timestamp remoteTimestamp = ad.linkTimestamps().get(fragId);
+ if (remoteTimestamp == null) {
+ remoteTimestamp = ad.linkTombstones().get(key);
+ }
+ if (remoteTimestamp == null ||
+ pDesc.isNewerThan(remoteTimestamp)) {
+ // I have more recent link description. update peer.
+ notifyPeer(sender, new InternalLinkEvent(providerId, pDesc));
+ } else {
+ final Timestamp remoteLive = ad.linkTimestamps().get(fragId);
+ if (remoteLive != null &&
+ remoteLive.compareTo(pDesc.timestamp()) > 0) {
+ // I have something outdated
+ localOutdated = true;
+ }
+ }
+
+ // search local latest along the way
+ if (localLatest == null ||
+ pDesc.isNewerThan(localLatest)) {
+ localLatest = pDesc.timestamp();
+ }
+ }
+ // Tests if remote remove is more recent then local latest.
+ final Timestamp remoteRemove = ad.linkTombstones().get(key);
+ if (remoteRemove != null) {
+ if (localLatest != null &&
+ localLatest.compareTo(remoteRemove) < 0) {
+ // remote remove is more recent
+ notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
+ }
+ }
+ }
+ }
+
+ // populate remove info if not known locally
+ for (Entry<LinkKey, Timestamp> remoteRm : ad.linkTombstones().entrySet()) {
+ final LinkKey key = remoteRm.getKey();
+ final Timestamp remoteRemove = remoteRm.getValue();
+ // relying on removeLinkInternal to ignore stale info
+ notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
+ }
+
+ if (localOutdated) {
+ // send back advertisement to speed up convergence
+ try {
+ unicastMessage(sender, LINK_ANTI_ENTROPY_ADVERTISEMENT,
+ createAdvertisement());
+ } catch (IOException e) {
+ log.debug("Failed to send back active advertisement");
+ }
+ }
+ }
+
+ private final class InternalLinkEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+
+ log.trace("Received link event from peer: {}", message.sender());
+ InternalLinkEvent event = (InternalLinkEvent) SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = event.providerId();
+ Timestamped<LinkDescription> linkDescription = event.linkDescription();
+
+ try {
+ notifyDelegateIfNotNull(createOrUpdateLinkInternal(providerId, linkDescription));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling link event", e);
+ }
+ }
+ }
+
+ private final class InternalLinkRemovedEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+
+ log.trace("Received link removed event from peer: {}", message.sender());
+ InternalLinkRemovedEvent event = (InternalLinkRemovedEvent) SERIALIZER.decode(message.payload());
+
+ LinkKey linkKey = event.linkKey();
+ Timestamp timestamp = event.timestamp();
+
+ try {
+ notifyDelegateIfNotNull(removeLinkInternal(linkKey, timestamp));
+ } catch (Exception e) {
+ log.warn("Exception thrown handling link removed", e);
+ }
+ }
+ }
+
+ private final class InternalLinkAntiEntropyAdvertisementListener
+ implements ClusterMessageHandler {
+
+ @Override
+ public void handle(ClusterMessage message) {
+ log.trace("Received Link Anti-Entropy advertisement from peer: {}", message.sender());
+ LinkAntiEntropyAdvertisement advertisement = SERIALIZER.decode(message.payload());
+ try {
+ handleAntiEntropyAdvertisement(advertisement);
+ } catch (Exception e) {
+ log.warn("Exception thrown while handling Link advertisements", e);
+ throw e;
+ }
+ }
+ }
+
+ private final class LinkInjectedEventListener
+ implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+
+ log.trace("Received injected link event from peer: {}", message.sender());
+ LinkInjectedEvent linkInjectedEvent = SERIALIZER.decode(message.payload());
+
+ ProviderId providerId = linkInjectedEvent.providerId();
+ LinkDescription linkDescription = linkInjectedEvent.linkDescription();
+
+ final DeviceId deviceId = linkDescription.dst().deviceId();
+ if (!deviceClockService.isTimestampAvailable(deviceId)) {
+ // workaround for ONOS-1208
+ log.warn("Not ready to accept update. Dropping {}", linkDescription);
+ return;
+ }
+
+ try {
+ createOrUpdateLink(providerId, linkDescription);
+ } catch (Exception e) {
+ log.warn("Exception thrown while handling link injected event", e);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStoreMessageSubjects.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStoreMessageSubjects.java
new file mode 100644
index 00000000..e0e1dda3
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/GossipLinkStoreMessageSubjects.java
@@ -0,0 +1,35 @@
+/*
+ * 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.store.link.impl;
+
+ import org.onosproject.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by GossipLinkStore peer-peer communication.
+ */
+public final class GossipLinkStoreMessageSubjects {
+
+ private GossipLinkStoreMessageSubjects() {}
+
+ public static final MessageSubject LINK_UPDATE =
+ new MessageSubject("peer-link-update");
+ public static final MessageSubject LINK_REMOVED =
+ new MessageSubject("peer-link-removed");
+ public static final MessageSubject LINK_ANTI_ENTROPY_ADVERTISEMENT =
+ new MessageSubject("link-enti-entropy-advertisement");
+ public static final MessageSubject LINK_INJECTED =
+ new MessageSubject("peer-link-injected");
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkEvent.java
new file mode 100644
index 00000000..2319f274
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.store.link.impl;
+
+import com.google.common.base.MoreObjects;
+
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.impl.Timestamped;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * change event.
+ */
+public class InternalLinkEvent {
+
+ private final ProviderId providerId;
+ private final Timestamped<LinkDescription> linkDescription;
+
+ protected InternalLinkEvent(
+ ProviderId providerId,
+ Timestamped<LinkDescription> linkDescription) {
+ this.providerId = providerId;
+ this.linkDescription = linkDescription;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public Timestamped<LinkDescription> linkDescription() {
+ return linkDescription;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("linkDescription", linkDescription)
+ .toString();
+ }
+
+ // for serializer
+ protected InternalLinkEvent() {
+ this.providerId = null;
+ this.linkDescription = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkRemovedEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkRemovedEvent.java
new file mode 100644
index 00000000..9d867203
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/InternalLinkRemovedEvent.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.link.impl;
+
+import org.onosproject.net.LinkKey;
+import org.onosproject.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Information published by GossipLinkStore to notify peers of a link
+ * being removed.
+ */
+public class InternalLinkRemovedEvent {
+
+ private final LinkKey linkKey;
+ private final Timestamp timestamp;
+
+ /**
+ * Creates a InternalLinkRemovedEvent.
+ * @param linkKey identifier of the removed link.
+ * @param timestamp timestamp of when the link was removed.
+ */
+ public InternalLinkRemovedEvent(LinkKey linkKey, Timestamp timestamp) {
+ this.linkKey = linkKey;
+ this.timestamp = timestamp;
+ }
+
+ public LinkKey linkKey() {
+ return linkKey;
+ }
+
+ public Timestamp timestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("linkKey", linkKey)
+ .add("timestamp", timestamp)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private InternalLinkRemovedEvent() {
+ linkKey = null;
+ timestamp = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkAntiEntropyAdvertisement.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkAntiEntropyAdvertisement.java
new file mode 100644
index 00000000..73c1042e
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkAntiEntropyAdvertisement.java
@@ -0,0 +1,63 @@
+/*
+ * 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.store.link.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.LinkKey;
+import org.onosproject.store.Timestamp;
+
+/**
+ * Link AE Advertisement message.
+ */
+public class LinkAntiEntropyAdvertisement {
+
+ private final NodeId sender;
+ private final Map<LinkFragmentId, Timestamp> linkTimestamps;
+ private final Map<LinkKey, Timestamp> linkTombstones;
+
+
+ public LinkAntiEntropyAdvertisement(NodeId sender,
+ Map<LinkFragmentId, Timestamp> linkTimestamps,
+ Map<LinkKey, Timestamp> linkTombstones) {
+ this.sender = checkNotNull(sender);
+ this.linkTimestamps = checkNotNull(linkTimestamps);
+ this.linkTombstones = checkNotNull(linkTombstones);
+ }
+
+ public NodeId sender() {
+ return sender;
+ }
+
+ public Map<LinkFragmentId, Timestamp> linkTimestamps() {
+ return linkTimestamps;
+ }
+
+ public Map<LinkKey, Timestamp> linkTombstones() {
+ return linkTombstones;
+ }
+
+ // For serializer
+ @SuppressWarnings("unused")
+ private LinkAntiEntropyAdvertisement() {
+ this.sender = null;
+ this.linkTimestamps = null;
+ this.linkTombstones = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkFragmentId.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkFragmentId.java
new file mode 100644
index 00000000..af7ce4fc
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkFragmentId.java
@@ -0,0 +1,77 @@
+/*
+ * 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.store.link.impl;
+
+import java.util.Objects;
+
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Identifier for LinkDescription from a Provider.
+ */
+public final class LinkFragmentId {
+ public final ProviderId providerId;
+ public final LinkKey linkKey;
+
+ public LinkFragmentId(LinkKey linkKey, ProviderId providerId) {
+ this.providerId = providerId;
+ this.linkKey = linkKey;
+ }
+
+ public LinkKey linkKey() {
+ return linkKey;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerId, linkKey);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof LinkFragmentId)) {
+ return false;
+ }
+ LinkFragmentId that = (LinkFragmentId) obj;
+ return Objects.equals(this.linkKey, that.linkKey) &&
+ Objects.equals(this.providerId, that.providerId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("linkKey", linkKey)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private LinkFragmentId() {
+ this.providerId = null;
+ this.linkKey = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkInjectedEvent.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkInjectedEvent.java
new file mode 100644
index 00000000..356033b0
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/LinkInjectedEvent.java
@@ -0,0 +1,38 @@
+package org.onosproject.store.link.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.provider.ProviderId;
+
+public class LinkInjectedEvent {
+
+ ProviderId providerId;
+ LinkDescription linkDescription;
+
+ public LinkInjectedEvent(ProviderId providerId, LinkDescription linkDescription) {
+ this.providerId = providerId;
+ this.linkDescription = linkDescription;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public LinkDescription linkDescription() {
+ return linkDescription;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("linkDescription", linkDescription)
+ .toString();
+ }
+
+ // for serializer
+ protected LinkInjectedEvent() {
+ this.providerId = null;
+ this.linkDescription = null;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/Provided.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/Provided.java
new file mode 100644
index 00000000..b5b9e644
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/Provided.java
@@ -0,0 +1,68 @@
+/*
+ * 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.store.link.impl;
+
+import java.util.Objects;
+
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Encapsulation of a provider supplied key.
+ *
+ * @param <K> key
+ */
+public class Provided<K> {
+ private final K key;
+ private final ProviderId providerId;
+
+ public Provided(K key, ProviderId providerId) {
+ this.key = key;
+ this.providerId = providerId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ public K key() {
+ return key;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, providerId);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Provided) {
+ Provided<K> that = (Provided) other;
+ return Objects.equals(key, that.key) &&
+ Objects.equals(providerId, that.providerId);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("key", key)
+ .add("providerId", providerId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/link/impl/package-info.java
new file mode 100644
index 00000000..97f2ccae
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of distributed link store using p2p synchronization protocol.
+ */
+package org.onosproject.store.link.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java
new file mode 100644
index 00000000..c6fc6933
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/ConsistentDeviceMastershipStore.java
@@ -0,0 +1,419 @@
+/*
+ * 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.store.mastership.impl;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onlab.util.Tools.futureGetOrElse;
+import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+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.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipStore;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.serializers.StoreSerializer;
+import org.slf4j.Logger;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * Implementation of the MastershipStore on top of Leadership Service.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ConsistentDeviceMastershipStore
+ extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
+ implements MastershipStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ private NodeId localNodeId;
+ private final Set<DeviceId> connectedDevices = Sets.newHashSet();
+
+ private static final MessageSubject ROLE_RELINQUISH_SUBJECT =
+ new MessageSubject("mastership-store-device-role-relinquish");
+ private static final MessageSubject TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT =
+ new MessageSubject("mastership-store-device-mastership-relinquish");
+
+ private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN =
+ Pattern.compile("device:(.*)");
+
+ private ExecutorService messageHandlingExecutor;
+ private ScheduledExecutorService transferExecutor;
+ private final LeadershipEventListener leadershipEventListener =
+ new InternalDeviceMastershipEventListener();
+
+ 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 int WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS = 3000;
+
+ public static final StoreSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(MastershipRole.class)
+ .register(MastershipEvent.class)
+ .register(MastershipEvent.Type.class)
+ .build();
+ }
+ };
+
+ @Activate
+ public void activate() {
+ messageHandlingExecutor =
+ Executors.newSingleThreadExecutor(
+ groupedThreads("onos/store/device/mastership", "message-handler"));
+ transferExecutor =
+ Executors.newSingleThreadScheduledExecutor(
+ groupedThreads("onos/store/device/mastership", "mastership-transfer-executor"));
+ clusterCommunicator.addSubscriber(ROLE_RELINQUISH_SUBJECT,
+ SERIALIZER::decode,
+ this::relinquishLocalRole,
+ SERIALIZER::encode,
+ messageHandlingExecutor);
+ clusterCommunicator.addSubscriber(TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT,
+ SERIALIZER::decode,
+ this::transitionFromMasterToStandby,
+ SERIALIZER::encode,
+ messageHandlingExecutor);
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.addListener(leadershipEventListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT);
+ clusterCommunicator.removeSubscriber(TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT);
+ messageHandlingExecutor.shutdown();
+ transferExecutor.shutdown();
+ leadershipService.removeListener(leadershipEventListener);
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRole(DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ if (connectedDevices.add(deviceId)) {
+ return leadershipService.runForLeadership(leadershipTopic)
+ .thenApply(leadership -> {
+ return Objects.equal(localNodeId, leadership.leader())
+ ? MastershipRole.MASTER : MastershipRole.STANDBY;
+ });
+ } else {
+ NodeId leader = leadershipService.getLeader(leadershipTopic);
+ if (Objects.equal(localNodeId, leader)) {
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ } else {
+ return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+ }
+ }
+ }
+
+ @Override
+ public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ NodeId leader = leadershipService.getLeader(leadershipTopic);
+ if (Objects.equal(nodeId, leader)) {
+ return MastershipRole.MASTER;
+ }
+ return leadershipService.getCandidates(leadershipTopic).contains(nodeId) ?
+ MastershipRole.STANDBY : MastershipRole.NONE;
+ }
+
+ @Override
+ public NodeId getMaster(DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ return leadershipService.getLeader(leadershipTopic);
+ }
+
+ @Override
+ public RoleInfo getNodes(DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ Map<NodeId, MastershipRole> roles = Maps.newHashMap();
+ clusterService
+ .getNodes()
+ .forEach((node) -> roles.put(node.id(), getRole(node.id(), deviceId)));
+
+ NodeId master = null;
+ final List<NodeId> standbys = Lists.newLinkedList();
+
+ List<NodeId> candidates = leadershipService.getCandidates(createDeviceMastershipTopic(deviceId));
+
+ for (Map.Entry<NodeId, MastershipRole> entry : roles.entrySet()) {
+ if (entry.getValue() == MastershipRole.MASTER) {
+ master = entry.getKey();
+ } else if (entry.getValue() == MastershipRole.STANDBY) {
+ standbys.add(entry.getKey());
+ }
+ }
+
+ List<NodeId> sortedStandbyList = candidates.stream().filter(standbys::contains).collect(Collectors.toList());
+
+ return new RoleInfo(master, sortedStandbyList);
+ }
+
+ @Override
+ public Set<DeviceId> getDevices(NodeId nodeId) {
+ checkArgument(nodeId != null, NODE_ID_NULL);
+
+ return leadershipService
+ .ownedTopics(nodeId)
+ .stream()
+ .filter(this::isDeviceMastershipTopic)
+ .map(this::extractDeviceIdFromTopic)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> setMaster(NodeId nodeId, DeviceId deviceId) {
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ NodeId currentMaster = getMaster(deviceId);
+ if (nodeId.equals(currentMaster)) {
+ return CompletableFuture.completedFuture(null);
+ } else {
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ List<NodeId> candidates = leadershipService.getCandidates(leadershipTopic);
+ if (candidates.isEmpty()) {
+ return CompletableFuture.completedFuture(null);
+ }
+ if (leadershipService.makeTopCandidate(leadershipTopic, nodeId)) {
+ CompletableFuture<MastershipEvent> result = new CompletableFuture<>();
+ // There is brief wait before we step down from mastership.
+ // This is to ensure any work that happens when standby preference
+ // order changes can complete. For example: flow entries need to be backed
+ // to the new top standby (ONOS-1883)
+ // FIXME: This potentially introduces a race-condition.
+ // Right now role changes are only forced via CLI.
+ transferExecutor.schedule(() -> {
+ result.complete(transitionFromMasterToStandby(deviceId));
+ }, WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS, TimeUnit.MILLISECONDS);
+ return result;
+ } else {
+ log.warn("Failed to promote {} to mastership for {}", nodeId, deviceId);
+ }
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public MastershipTerm getTermFor(DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ Leadership leadership = leadershipService.getLeadership(leadershipTopic);
+ return leadership != null ? MastershipTerm.of(leadership.leader(), leadership.epoch()) : null;
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> setStandby(NodeId nodeId, DeviceId deviceId) {
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ NodeId currentMaster = getMaster(deviceId);
+ if (!nodeId.equals(currentMaster)) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ List<NodeId> candidates = leadershipService.getCandidates(leadershipTopic);
+
+ NodeId newMaster = candidates.stream()
+ .filter(candidate -> !Objects.equal(nodeId, candidate))
+ .findFirst()
+ .orElse(null);
+ log.info("Transitioning to role {} for {}. Next master: {}",
+ newMaster != null ? MastershipRole.STANDBY : MastershipRole.NONE, deviceId, newMaster);
+
+ if (newMaster != null) {
+ return setMaster(newMaster, deviceId);
+ }
+ return relinquishRole(nodeId, deviceId);
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> relinquishRole(NodeId nodeId, DeviceId deviceId) {
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ if (nodeId.equals(localNodeId)) {
+ return relinquishLocalRole(deviceId);
+ }
+
+ log.debug("Forwarding request to relinquish "
+ + "role for device {} to {}", deviceId, nodeId);
+ return clusterCommunicator.sendAndReceive(
+ deviceId,
+ ROLE_RELINQUISH_SUBJECT,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ nodeId);
+ }
+
+ private CompletableFuture<MastershipEvent> relinquishLocalRole(DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ // Check if this node is can be managed by this node.
+ if (!connectedDevices.contains(deviceId)) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ String leadershipTopic = createDeviceMastershipTopic(deviceId);
+ NodeId currentLeader = leadershipService.getLeader(leadershipTopic);
+
+ MastershipEvent.Type eventType = Objects.equal(currentLeader, localNodeId)
+ ? MastershipEvent.Type.MASTER_CHANGED
+ : MastershipEvent.Type.BACKUPS_CHANGED;
+
+ connectedDevices.remove(deviceId);
+ return leadershipService.withdraw(leadershipTopic)
+ .thenApply(v -> new MastershipEvent(eventType, deviceId, getNodes(deviceId)));
+ }
+
+ private MastershipEvent transitionFromMasterToStandby(DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ NodeId currentMaster = getMaster(deviceId);
+ if (currentMaster == null) {
+ return null;
+ }
+
+ if (!currentMaster.equals(localNodeId)) {
+ log.info("Forwarding request to relinquish "
+ + "mastership for device {} to {}", deviceId, currentMaster);
+ return futureGetOrElse(clusterCommunicator.sendAndReceive(
+ deviceId,
+ TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ currentMaster), null);
+ }
+
+ return leadershipService.stepdown(createDeviceMastershipTopic(deviceId))
+ ? new MastershipEvent(MastershipEvent.Type.MASTER_CHANGED, deviceId, getNodes(deviceId)) : null;
+ }
+
+ @Override
+ public void relinquishAllRole(NodeId nodeId) {
+ // Noop. LeadershipService already takes care of detecting and purging deadlocks.
+ }
+
+ private class InternalDeviceMastershipEventListener implements LeadershipEventListener {
+ @Override
+ public void event(LeadershipEvent event) {
+ Leadership leadership = event.subject();
+ if (!isDeviceMastershipTopic(leadership.topic())) {
+ return;
+ }
+ DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic());
+ switch (event.type()) {
+ case LEADER_ELECTED:
+ notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
+ break;
+ case LEADER_REELECTED:
+ // There is no concept of leader re-election in the new distributed leadership manager.
+ throw new IllegalStateException("Unexpected event type");
+ case LEADER_BOOTED:
+ notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
+ break;
+ case CANDIDATES_CHANGED:
+ notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId)));
+ break;
+ default:
+ return;
+ }
+ }
+ }
+
+ private String createDeviceMastershipTopic(DeviceId deviceId) {
+ return String.format("device:%s", deviceId.toString());
+ }
+
+ private DeviceId extractDeviceIdFromTopic(String topic) {
+ Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+ if (m.matches()) {
+ return DeviceId.deviceId(m.group(1));
+ } else {
+ throw new IllegalArgumentException("Invalid device mastership topic: " + topic);
+ }
+ }
+
+ private boolean isDeviceMastershipTopic(String topic) {
+ Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+ return m.matches();
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValue.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValue.java
new file mode 100644
index 00000000..9d3b1686
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValue.java
@@ -0,0 +1,179 @@
+/*
+ * 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.store.mastership.impl;
+
+import static org.onosproject.net.MastershipRole.MASTER;
+import static org.onosproject.net.MastershipRole.NONE;
+import static org.onosproject.net.MastershipRole.STANDBY;
+
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.net.MastershipRole;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.Lists;
+
+/**
+ * A structure that holds node mastership roles associated with a
+ * {@link org.onosproject.net.DeviceId}. This structure needs to be locked through IMap.
+ */
+final class RoleValue {
+
+ protected final Map<MastershipRole, List<NodeId>> value = new EnumMap<>(MastershipRole.class);
+
+ /**
+ * Constructs empty RoleValue.
+ */
+ public RoleValue() {
+ value.put(MastershipRole.MASTER, new LinkedList<NodeId>());
+ value.put(MastershipRole.STANDBY, new LinkedList<NodeId>());
+ value.put(MastershipRole.NONE, new LinkedList<NodeId>());
+ }
+
+ /**
+ * Constructs copy of specified RoleValue.
+ *
+ * @param original original to create copy from
+ */
+ public RoleValue(final RoleValue original) {
+ value.put(MASTER, Lists.newLinkedList(original.value.get(MASTER)));
+ value.put(STANDBY, Lists.newLinkedList(original.value.get(STANDBY)));
+ value.put(NONE, Lists.newLinkedList(original.value.get(NONE)));
+ }
+
+ // exposing internals for serialization purpose only
+ Map<MastershipRole, List<NodeId>> value() {
+ return Collections.unmodifiableMap(value);
+ }
+
+ public List<NodeId> nodesOfRole(MastershipRole type) {
+ return value.get(type);
+ }
+
+ /**
+ * Returns the first node to match the MastershipRole, or if there
+ * are none, null.
+ *
+ * @param type the role
+ * @return a node ID or null
+ */
+ public NodeId get(MastershipRole type) {
+ return value.get(type).isEmpty() ? null : value.get(type).get(0);
+ }
+
+ public boolean contains(MastershipRole type, NodeId nodeId) {
+ return value.get(type).contains(nodeId);
+ }
+
+ public MastershipRole getRole(NodeId nodeId) {
+ if (contains(MASTER, nodeId)) {
+ return MASTER;
+ }
+ if (contains(STANDBY, nodeId)) {
+ return STANDBY;
+ }
+ return NONE;
+ }
+
+ /**
+ * Associates a node to a certain role.
+ *
+ * @param type the role
+ * @param nodeId the node ID of the node to associate
+ * @return true if modified
+ */
+ public boolean add(MastershipRole type, NodeId nodeId) {
+ List<NodeId> nodes = value.get(type);
+
+ if (!nodes.contains(nodeId)) {
+ return nodes.add(nodeId);
+ }
+ return false;
+ }
+
+ /**
+ * Removes a node from a certain role.
+ *
+ * @param type the role
+ * @param nodeId the ID of the node to remove
+ * @return true if modified
+ */
+ public boolean remove(MastershipRole type, NodeId nodeId) {
+ List<NodeId> nodes = value.get(type);
+ if (!nodes.isEmpty()) {
+ return nodes.remove(nodeId);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Reassigns a node from one role to another. If the node was not of the
+ * old role, it will still be assigned the new role.
+ *
+ * @param nodeId the Node ID of node changing roles
+ * @param from the old role
+ * @param to the new role
+ * @return true if modified
+ */
+ public boolean reassign(NodeId nodeId, MastershipRole from, MastershipRole to) {
+ boolean modified = remove(from, nodeId);
+ modified |= add(to, nodeId);
+ return modified;
+ }
+
+ /**
+ * Replaces a node in one role with another node. Even if there is no node to
+ * replace, the new node is associated to the role.
+ *
+ * @param from the old NodeId to replace
+ * @param to the new NodeId
+ * @param type the role associated with the old NodeId
+ * @return true if modified
+ */
+ public boolean replace(NodeId from, NodeId to, MastershipRole type) {
+ boolean modified = remove(type, from);
+ modified |= add(type, to);
+ return modified;
+ }
+
+ /**
+ * Summarizes this RoleValue as a RoleInfo. Note that master and/or backups
+ * may be empty, so the values should be checked for safety.
+ *
+ * @return the RoleInfo.
+ */
+ public RoleInfo roleInfo() {
+ return new RoleInfo(
+ get(MastershipRole.MASTER), nodesOfRole(MastershipRole.STANDBY));
+ }
+
+ @Override
+ public String toString() {
+ ToStringHelper helper = MoreObjects.toStringHelper(this.getClass());
+ for (Map.Entry<MastershipRole, List<NodeId>> el : value.entrySet()) {
+ helper.add(el.getKey().toString(), el.getValue());
+ }
+ return helper.toString();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValueSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValueSerializer.java
new file mode 100644
index 00000000..c81ea7f9
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/RoleValueSerializer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.store.mastership.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.MastershipRole;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Serializer for RoleValues used by {@link org.onosproject.mastership.MastershipStore}.
+ */
+public class RoleValueSerializer extends Serializer<RoleValue> {
+
+ //RoleValues are assumed to hold a Map of MastershipRoles (an enum)
+ //to a List of NodeIds.
+
+ @Override
+ public RoleValue read(Kryo kryo, Input input, Class<RoleValue> type) {
+ RoleValue rv = new RoleValue();
+ int size = input.readInt();
+ for (int i = 0; i < size; i++) {
+ MastershipRole role = MastershipRole.values()[input.readInt()];
+ int s = input.readInt();
+ for (int j = 0; j < s; j++) {
+ rv.add(role, new NodeId(input.readString()));
+ }
+ }
+ return rv;
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, RoleValue type) {
+ final Map<MastershipRole, List<NodeId>> map = type.value();
+ output.writeInt(map.size());
+
+ for (Map.Entry<MastershipRole, List<NodeId>> el : map.entrySet()) {
+ output.writeInt(el.getKey().ordinal());
+
+ List<NodeId> nodes = el.getValue();
+ output.writeInt(nodes.size());
+ for (NodeId n : nodes) {
+ output.writeString(n.toString());
+ }
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/impl/package-info.java
new file mode 100644
index 00000000..40ff6f76
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/mastership/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.
+ */
+
+/**
+ * Implementation of a distributed mastership store using Hazelcast.
+ */
+package org.onosproject.store.mastership.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java
new file mode 100644
index 00000000..648119e5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java
@@ -0,0 +1,349 @@
+/*
+ * 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.store.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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.net.newresource.ResourceConsumer;
+import org.onosproject.net.newresource.ResourcePath;
+import org.onosproject.net.newresource.ResourceStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TransactionContext;
+import org.onosproject.store.service.TransactionException;
+import org.onosproject.store.service.TransactionalMap;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+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;
+
+/**
+ * Implementation of ResourceStore using TransactionalMap.
+ */
+@Component(immediate = true, enabled = false)
+@Service
+@Beta
+public class ConsistentResourceStore implements ResourceStore {
+ private static final Logger log = LoggerFactory.getLogger(ConsistentResourceStore.class);
+
+ private static final String CONSUMER_MAP = "onos-resource-consumers";
+ private static final String CHILD_MAP = "onos-resource-children";
+ private static final Serializer SERIALIZER = Serializer.using(
+ Arrays.asList(KryoNamespaces.BASIC, KryoNamespaces.API));
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService service;
+
+ private ConsistentMap<ResourcePath, ResourceConsumer> consumerMap;
+ private ConsistentMap<ResourcePath, List<ResourcePath>> childMap;
+
+ @Activate
+ public void activate() {
+ consumerMap = service.<ResourcePath, ResourceConsumer>consistentMapBuilder()
+ .withName(CONSUMER_MAP)
+ .withSerializer(SERIALIZER)
+ .build();
+ childMap = service.<ResourcePath, List<ResourcePath>>consistentMapBuilder()
+ .withName(CHILD_MAP)
+ .withSerializer(SERIALIZER)
+ .build();
+ }
+
+ @Override
+ public Optional<ResourceConsumer> getConsumer(ResourcePath resource) {
+ checkNotNull(resource);
+
+ Versioned<ResourceConsumer> consumer = consumerMap.get(resource);
+ if (consumer == null) {
+ return Optional.empty();
+ }
+
+ return Optional.of(consumer.value());
+ }
+
+ @Override
+ public boolean register(List<ResourcePath> resources) {
+ checkNotNull(resources);
+
+ TransactionContext tx = service.transactionContextBuilder().build();
+ tx.begin();
+
+ try {
+ TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
+ tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
+
+ Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
+ .filter(x -> x.parent().isPresent())
+ .collect(Collectors.groupingBy(x -> x.parent().get()));
+
+ for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
+ if (!isRegistered(childTxMap, entry.getKey())) {
+ return abortTransaction(tx);
+ }
+
+ if (!appendValues(childTxMap, entry.getKey(), entry.getValue())) {
+ return abortTransaction(tx);
+ }
+ }
+
+ return commitTransaction(tx);
+ } catch (TransactionException e) {
+ log.error("Exception thrown, abort the transaction", e);
+ return abortTransaction(tx);
+ }
+ }
+
+ @Override
+ public boolean unregister(List<ResourcePath> resources) {
+ checkNotNull(resources);
+
+ TransactionContext tx = service.transactionContextBuilder().build();
+ tx.begin();
+
+ try {
+ TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
+ tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
+ TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
+ tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
+
+ Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
+ .filter(x -> x.parent().isPresent())
+ .collect(Collectors.groupingBy(x -> x.parent().get()));
+
+ // even if one of the resources is allocated to a consumer,
+ // all unregistrations are regarded as failure
+ for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
+ if (entry.getValue().stream().anyMatch(x -> consumerTxMap.get(x) != null)) {
+ return abortTransaction(tx);
+ }
+
+ if (!removeValues(childTxMap, entry.getKey(), entry.getValue())) {
+ return abortTransaction(tx);
+ }
+ }
+
+ return commitTransaction(tx);
+ } catch (TransactionException e) {
+ log.error("Exception thrown, abort the transaction", e);
+ return abortTransaction(tx);
+ }
+ }
+
+ @Override
+ public boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer) {
+ checkNotNull(resources);
+ checkNotNull(consumer);
+
+ TransactionContext tx = service.transactionContextBuilder().build();
+ tx.begin();
+
+ try {
+ TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
+ tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
+ TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
+ tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
+
+ for (ResourcePath resource: resources) {
+ if (!isRegistered(childTxMap, resource)) {
+ return abortTransaction(tx);
+ }
+
+ ResourceConsumer oldValue = consumerTxMap.put(resource, consumer);
+ if (oldValue != null) {
+ return abortTransaction(tx);
+ }
+ }
+
+ return commitTransaction(tx);
+ } catch (TransactionException e) {
+ log.error("Exception thrown, abort the transaction", e);
+ return abortTransaction(tx);
+ }
+ }
+
+ @Override
+ public boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers) {
+ checkNotNull(resources);
+ checkNotNull(consumers);
+ checkArgument(resources.size() == consumers.size());
+
+ TransactionContext tx = service.transactionContextBuilder().build();
+ tx.begin();
+
+ try {
+ TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
+ tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
+ Iterator<ResourcePath> resourceIte = resources.iterator();
+ Iterator<ResourceConsumer> consumerIte = consumers.iterator();
+
+ while (resourceIte.hasNext() && consumerIte.hasNext()) {
+ ResourcePath resource = resourceIte.next();
+ ResourceConsumer consumer = consumerIte.next();
+
+ // if this single release fails (because the resource is allocated to another consumer,
+ // the whole release fails
+ if (!consumerTxMap.remove(resource, consumer)) {
+ return abortTransaction(tx);
+ }
+ }
+
+ return commitTransaction(tx);
+ } catch (TransactionException e) {
+ log.error("Exception thrown, abort the transaction", e);
+ return abortTransaction(tx);
+ }
+ }
+
+ @Override
+ public Collection<ResourcePath> getResources(ResourceConsumer consumer) {
+ checkNotNull(consumer);
+
+ // NOTE: getting all entries may become performance bottleneck
+ // TODO: revisit for better backend data structure
+ return consumerMap.entrySet().stream()
+ .filter(x -> x.getValue().value().equals(consumer))
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
+ checkNotNull(parent);
+ checkNotNull(cls);
+
+ Versioned<List<ResourcePath>> children = childMap.get(parent);
+ if (children == null) {
+ return Collections.emptyList();
+ }
+
+ return children.value().stream()
+ .filter(x -> x.lastComponent().getClass().equals(cls))
+ .filter(consumerMap::containsKey)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Abort the transaction.
+ *
+ * @param tx transaction context
+ * @return always false
+ */
+ private boolean abortTransaction(TransactionContext tx) {
+ tx.abort();
+ return false;
+ }
+
+ /**
+ * Commit the transaction.
+ *
+ * @param tx transaction context
+ * @return always true
+ */
+ private boolean commitTransaction(TransactionContext tx) {
+ tx.commit();
+ return true;
+ }
+
+ /**
+ * Appends the values to the existing values associated with the specified key.
+ * If the map already has all the given values, appending will not happen.
+ *
+ * @param map map holding multiple values for a key
+ * @param key key specifying values
+ * @param values values to be appended
+ * @param <K> type of the key
+ * @param <V> type of the element of the list
+ * @return true if the operation succeeds, false otherwise.
+ */
+ private <K, V> boolean appendValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
+ List<V> oldValues = map.get(key);
+ if (oldValues == null) {
+ return map.replace(key, oldValues, new ArrayList<>(values));
+ }
+
+ LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
+ if (oldSet.containsAll(values)) {
+ // don't write to map because all values are already stored
+ return true;
+ }
+
+ oldSet.addAll(values);
+ return map.replace(key, oldValues, new ArrayList<>(oldSet));
+ }
+
+ /**
+ * Removes teh values from the existing values associated with the specified key.
+ * If the map doesn't contain the given values, removal will not happen.
+ *
+ * @param map map holding multiple values for a key
+ * @param key key specifying values
+ * @param values values to be removed
+ * @param <K> type of the key
+ * @param <V> type of the element of the list
+ * @return true if the operation succeeds, false otherwise
+ */
+ private <K, V> boolean removeValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
+ List<V> oldValues = map.get(key);
+ if (oldValues == null) {
+ return map.replace(key, oldValues, new ArrayList<>());
+ }
+
+ LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
+ if (values.stream().allMatch(x -> !oldSet.contains(x))) {
+ // don't write map because none of the values are stored
+ return true;
+ }
+
+ oldSet.removeAll(values);
+ return map.replace(key, oldValues, new ArrayList<>(oldSet));
+ }
+
+ /**
+ * Checks if the specified resource is registered as a child of a resource in the map.
+ *
+ * @param map map storing parent - child relationship of resources
+ * @param resource resource to be checked
+ * @return true if the resource is registered, false otherwise.
+ */
+ private boolean isRegistered(TransactionalMap<ResourcePath, List<ResourcePath>> map, ResourcePath resource) {
+ // root is always regarded to be registered
+ if (resource.isRoot()) {
+ return true;
+ }
+
+ List<ResourcePath> value = map.get(resource.parent().get());
+ return value != null && value.contains(resource);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/package-info.java
new file mode 100644
index 00000000..330d56c3
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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 network resource distributed store.
+ */
+package org.onosproject.store.newresource.impl; \ No newline at end of file
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/DistributedPacketStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/DistributedPacketStore.java
new file mode 100644
index 00000000..24ce2155
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/DistributedPacketStore.java
@@ -0,0 +1,207 @@
+/*
+ * 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.store.packet.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.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketEvent.Type;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStore;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed packet store implementation allowing packets to be sent to
+ * remote instances.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedPacketStore
+ extends AbstractStore<PacketEvent, PacketStoreDelegate>
+ implements PacketStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // TODO: make this configurable.
+ private static final int MESSAGE_HANDLER_THREAD_POOL_SIZE = 4;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService communicationService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private PacketRequestTracker tracker;
+
+ private static final MessageSubject PACKET_OUT_SUBJECT =
+ new MessageSubject("packet-out");
+
+ private static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .build();
+ }
+ };
+
+ private ExecutorService messageHandlingExecutor;
+
+ @Activate
+ public void activate() {
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ MESSAGE_HANDLER_THREAD_POOL_SIZE,
+ groupedThreads("onos/store/packet", "message-handlers"));
+
+ communicationService.<OutboundPacket>addSubscriber(PACKET_OUT_SUBJECT,
+ SERIALIZER::decode,
+ packet -> notifyDelegate(new PacketEvent(Type.EMIT, packet)),
+ messageHandlingExecutor);
+
+ tracker = new PacketRequestTracker();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ communicationService.removeSubscriber(PACKET_OUT_SUBJECT);
+ messageHandlingExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ NodeId myId = clusterService.getLocalNode().id();
+ NodeId master = mastershipService.getMasterFor(packet.sendThrough());
+
+ if (master == null) {
+ return;
+ }
+
+ if (myId.equals(master)) {
+ notifyDelegate(new PacketEvent(Type.EMIT, packet));
+ return;
+ }
+
+ communicationService.unicast(packet, PACKET_OUT_SUBJECT, SERIALIZER::encode, master)
+ .whenComplete((r, error) -> {
+ if (error != null) {
+ log.warn("Failed to send packet-out to {}", master, error);
+ }
+ });
+ }
+
+ @Override
+ public boolean requestPackets(PacketRequest request) {
+ return tracker.add(request);
+ }
+
+ @Override
+ public boolean cancelPackets(PacketRequest request) {
+ return tracker.remove(request);
+ }
+
+ @Override
+ public Set<PacketRequest> existingRequests() {
+ return tracker.requests();
+ }
+
+ private class PacketRequestTracker {
+
+ private ConsistentMap<TrafficSelector, Set<PacketRequest>> requests;
+
+ public PacketRequestTracker() {
+ requests = storageService.<TrafficSelector, Set<PacketRequest>>consistentMapBuilder()
+ .withName("onos-packet-requests")
+ .withPartitionsDisabled()
+ .withSerializer(Serializer.using(KryoNamespaces.API))
+ .build();
+ }
+
+ public boolean add(PacketRequest request) {
+ Versioned<Set<PacketRequest>> old = requests.get(request.selector());
+ if (old != null && old.value().contains(request)) {
+ return false;
+ }
+ // FIXME: add retry logic using a random delay
+ Set<PacketRequest> newSet = new HashSet<>();
+ newSet.add(request);
+ if (old == null) {
+ return requests.putIfAbsent(request.selector(), newSet) == null;
+ }
+ newSet.addAll(old.value());
+ return requests.replace(request.selector(), old.version(), newSet);
+ }
+
+ public boolean remove(PacketRequest request) {
+ Versioned<Set<PacketRequest>> old = requests.get(request.selector());
+ if (old == null || !old.value().contains(request)) {
+ return false;
+ }
+ // FIXME: add retry logic using a random delay
+ Set<PacketRequest> newSet = new HashSet<>(old.value());
+ newSet.remove(request);
+ if (newSet.isEmpty()) {
+ return requests.remove(request.selector(), old.version());
+ }
+ return requests.replace(request.selector(), old.version(), newSet);
+ }
+
+ public Set<PacketRequest> requests() {
+ ImmutableSet.Builder<PacketRequest> builder = ImmutableSet.builder();
+ requests.values().forEach(v -> builder.addAll(v.value()));
+ return builder.build();
+ }
+
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/impl/package-info.java
new file mode 100644
index 00000000..43282583
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/packet/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.
+ */
+
+/**
+ * Implementation of distributed packet store.
+ */
+package org.onosproject.store.packet.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java
new file mode 100644
index 00000000..851185b5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java
@@ -0,0 +1,174 @@
+/*
+ * 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.store.proxyarp.impl;
+
+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.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.proxyarp.ProxyArpStore;
+import org.onosproject.net.proxyarp.ProxyArpStoreDelegate;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Implementation of proxy ARP distribution mechanism.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedProxyArpStore implements ProxyArpStore {
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final MessageSubject ARP_RESPONSE_MESSAGE =
+ new MessageSubject("onos-arp-response");
+
+ protected final KryoSerializer serializer = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(ArpResponseMessage.class)
+ .register(ByteBuffer.class)
+ .build();
+ }
+ };
+
+ private ProxyArpStoreDelegate delegate;
+
+ private Map<HostId, ArpResponseMessage> pendingMessages = Maps.newConcurrentMap();
+
+ private ExecutorService executor =
+ newFixedThreadPool(4, groupedThreads("onos/arp", "sender-%d"));
+
+ private NodeId localNodeId;
+
+ private HostListener hostListener = new InternalHostListener();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService commService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+
+ @Activate
+ protected void activate() {
+ localNodeId = clusterService.getLocalNode().id();
+ hostService.addListener(hostListener);
+ commService.addSubscriber(ARP_RESPONSE_MESSAGE, serializer::decode,
+ this::processArpResponse, executor);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ commService.removeSubscriber(ARP_RESPONSE_MESSAGE);
+ hostService.removeListener(hostListener);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void forward(ConnectPoint outPort, Host subject, ByteBuffer packet) {
+ NodeId nodeId = mastershipService.getMasterFor(outPort.deviceId());
+ if (nodeId.equals(localNodeId)) {
+ if (delegate != null) {
+ delegate.emitResponse(outPort, packet);
+ }
+ } else {
+ log.info("Forwarding ARP response from {} to {}", subject.id(), outPort);
+ commService.unicast(new ArpResponseMessage(outPort, subject, packet.array()),
+ ARP_RESPONSE_MESSAGE, serializer::encode, nodeId);
+ }
+ }
+
+ @Override
+ public void setDelegate(ProxyArpStoreDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ // Processes the incoming ARP response message.
+ private void processArpResponse(ArpResponseMessage msg) {
+ pendingMessages.put(msg.subject.id(), msg);
+ if (hostService.getHost(msg.subject.id()) != null) {
+ checkPendingArps(msg.subject.id());
+ }
+ // FIXME: figure out pruning so stuff does not build up
+ }
+
+ // Checks for pending ARP response message for the specified host.
+ // If one exists, emit response via delegate.
+ private void checkPendingArps(HostId id) {
+ ArpResponseMessage msg = pendingMessages.remove(id);
+ if (msg != null && delegate != null) {
+ log.info("Emitting ARP response from {} to {}", id, msg.outPort);
+ delegate.emitResponse(msg.outPort, ByteBuffer.wrap(msg.packet));
+ }
+ }
+
+ // Message carrying an ARP response.
+ private static class ArpResponseMessage {
+ private ConnectPoint outPort;
+ private Host subject;
+ private byte[] packet;
+
+ public ArpResponseMessage(ConnectPoint outPort, Host subject, byte[] packet) {
+ this.outPort = outPort;
+ this.subject = subject;
+ this.packet = packet;
+ }
+
+ private ArpResponseMessage() {
+ }
+ }
+
+ private class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ checkPendingArps(event.subject().id());
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java
new file mode 100644
index 00000000..3266e96c
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java
@@ -0,0 +1,225 @@
+/*
+ * 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.store.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.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.resource.device.DeviceResourceStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TransactionContext;
+import org.onosproject.store.service.TransactionalMap;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Store that manages device resources using Copycat-backed TransactionalMaps.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ConsistentDeviceResourceStore implements DeviceResourceStore {
+ private final Logger log = getLogger(getClass());
+
+ private static final String PORT_ALLOCATIONS = "PortAllocations";
+ private static final String INTENT_MAPPING = "IntentMapping";
+ private static final String INTENT_ALLOCATIONS = "PortIntentAllocations";
+
+ private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
+
+ private ConsistentMap<Port, IntentId> portAllocMap;
+ private ConsistentMap<IntentId, Set<Port>> intentAllocMap;
+ private ConsistentMap<IntentId, Set<IntentId>> intentMapping;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Activate
+ public void activate() {
+ portAllocMap = storageService.<Port, IntentId>consistentMapBuilder()
+ .withName(PORT_ALLOCATIONS)
+ .withSerializer(SERIALIZER)
+ .build();
+ intentAllocMap = storageService.<IntentId, Set<Port>>consistentMapBuilder()
+ .withName(INTENT_ALLOCATIONS)
+ .withSerializer(SERIALIZER)
+ .build();
+ intentMapping = storageService.<IntentId, Set<IntentId>>consistentMapBuilder()
+ .withName(INTENT_MAPPING)
+ .withSerializer(SERIALIZER)
+ .build();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ private TransactionalMap<Port, IntentId> getPortAllocs(TransactionContext tx) {
+ return tx.getTransactionalMap(PORT_ALLOCATIONS, SERIALIZER);
+ }
+
+ private TransactionalMap<IntentId, Set<Port>> getIntentAllocs(TransactionContext tx) {
+ return tx.getTransactionalMap(INTENT_ALLOCATIONS, SERIALIZER);
+ }
+
+ private TransactionContext getTxContext() {
+ return storageService.transactionContextBuilder().build();
+ }
+
+ @Override
+ public Set<Port> getFreePorts(DeviceId deviceId) {
+ checkNotNull(deviceId);
+
+ Set<Port> freePorts = new HashSet<>();
+ for (Port port : deviceService.getPorts(deviceId)) {
+ if (!portAllocMap.containsKey(port)) {
+ freePorts.add(port);
+ }
+ }
+
+ return freePorts;
+ }
+
+ @Override
+ public boolean allocatePorts(Set<Port> ports, IntentId intentId) {
+ checkNotNull(ports);
+ checkArgument(ports.size() > 0);
+ checkNotNull(intentId);
+
+ TransactionContext tx = getTxContext();
+ tx.begin();
+ try {
+ TransactionalMap<Port, IntentId> portAllocs = getPortAllocs(tx);
+ for (Port port : ports) {
+ if (portAllocs.putIfAbsent(port, intentId) != null) {
+ throw new Exception("Port already allocated " + port.toString());
+ }
+ }
+
+ TransactionalMap<IntentId, Set<Port>> intentAllocs = getIntentAllocs(tx);
+ intentAllocs.put(intentId, ports);
+ tx.commit();
+ } catch (Exception e) {
+ log.error("Exception thrown, rolling back", e);
+ tx.abort();
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Set<Port> getAllocations(IntentId intentId) {
+ if (!intentAllocMap.containsKey(intentId)) {
+ Collections.emptySet();
+ }
+
+ return intentAllocMap.get(intentId).value();
+ }
+
+ @Override
+ public IntentId getAllocations(Port port) {
+ if (!portAllocMap.containsKey(port)) {
+ return null;
+ }
+
+ return portAllocMap.get(port).value();
+ }
+
+ @Override
+ public Set<IntentId> getMapping(IntentId intentId) {
+ Versioned<Set<IntentId>> result = intentMapping.get(intentId);
+
+ if (result != null) {
+ return result.value();
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean allocateMapping(IntentId keyIntentId, IntentId valIntentId) {
+ Versioned<Set<IntentId>> versionedIntents = intentMapping.get(keyIntentId);
+
+ if (versionedIntents == null) {
+ Set<IntentId> newSet = new HashSet<>();
+ newSet.add(valIntentId);
+ intentMapping.put(keyIntentId, newSet);
+ } else {
+ versionedIntents.value().add(valIntentId);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void releaseMapping(IntentId intentId) {
+ for (IntentId intent : intentMapping.keySet()) {
+ // TODO: optimize by checking for identical src & dst
+ Set<IntentId> mapping = intentMapping.get(intent).value();
+ if (mapping.remove(intentId)) {
+ return;
+ }
+ }
+ }
+
+ @Override
+ public boolean releasePorts(IntentId intentId) {
+ checkNotNull(intentId);
+
+ TransactionContext tx = getTxContext();
+ tx.begin();
+ try {
+ TransactionalMap<IntentId, Set<Port>> intentAllocs = getIntentAllocs(tx);
+ Set<Port> ports = intentAllocs.get(intentId);
+ intentAllocs.remove(intentId);
+
+ TransactionalMap<Port, IntentId> portAllocs = getPortAllocs(tx);
+ for (Port port : ports) {
+ portAllocs.remove(port);
+ }
+ tx.commit();
+ } catch (Exception e) {
+ log.error("Exception thrown, rolling back", e);
+ tx.abort();
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentLinkResourceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentLinkResourceStore.java
new file mode 100644
index 00000000..ce25f868
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentLinkResourceStore.java
@@ -0,0 +1,503 @@
+package org.onosproject.store.resource.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+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.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.OmsPort;
+import org.onosproject.net.device.DeviceService;
+import org.slf4j.Logger;
+import org.onlab.util.PositionalParameterStringFormatter;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.Port;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.BandwidthResourceAllocation;
+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.LinkResourceEvent;
+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.ResourceAllocation;
+import org.onosproject.net.resource.ResourceAllocationException;
+import org.onosproject.net.resource.ResourceType;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TransactionContext;
+import org.onosproject.store.service.TransactionException;
+import org.onosproject.store.service.TransactionalMap;
+import org.onosproject.store.service.Versioned;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.net.AnnotationKeys.BANDWIDTH;
+
+/**
+ * Store that manages link resources using Copycat-backed TransactionalMaps.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ConsistentLinkResourceStore extends
+ AbstractStore<LinkResourceEvent, LinkResourceStoreDelegate> implements
+ LinkResourceStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final BandwidthResource DEFAULT_BANDWIDTH = new BandwidthResource(Bandwidth.mbps(1_000));
+ private static final BandwidthResource EMPTY_BW = new BandwidthResource(Bandwidth.bps(0));
+
+ // Smallest non-reserved MPLS label
+ private static final int MIN_UNRESERVED_LABEL = 0x10;
+ // Max non-reserved MPLS label = 239
+ private static final int MAX_UNRESERVED_LABEL = 0xEF;
+
+ // table to store current allocations
+ /** LinkKey -> List<LinkResourceAllocations>. */
+ private static final String LINK_RESOURCE_ALLOCATIONS = "LinkAllocations";
+
+ /** IntentId -> LinkResourceAllocations. */
+ private static final String INTENT_ALLOCATIONS = "LinkIntentAllocations";
+
+ private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
+
+ // for reading committed values.
+ private ConsistentMap<IntentId, LinkResourceAllocations> intentAllocMap;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LinkService linkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Activate
+ public void activate() {
+ intentAllocMap = storageService.<IntentId, LinkResourceAllocations>consistentMapBuilder()
+ .withName(INTENT_ALLOCATIONS)
+ .withSerializer(SERIALIZER)
+ .build();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ private TransactionalMap<IntentId, LinkResourceAllocations> getIntentAllocs(TransactionContext tx) {
+ return tx.getTransactionalMap(INTENT_ALLOCATIONS, SERIALIZER);
+ }
+
+ private TransactionalMap<LinkKey, List<LinkResourceAllocations>> getLinkAllocs(TransactionContext tx) {
+ return tx.getTransactionalMap(LINK_RESOURCE_ALLOCATIONS, SERIALIZER);
+ }
+
+ private TransactionContext getTxContext() {
+ return storageService.transactionContextBuilder().build();
+ }
+
+ private Set<? extends ResourceAllocation> getResourceCapacity(ResourceType type, Link link) {
+ if (type == ResourceType.BANDWIDTH) {
+ return ImmutableSet.of(getBandwidthResourceCapacity(link));
+ }
+ if (type == ResourceType.LAMBDA) {
+ return getLambdaResourceCapacity(link);
+ }
+ if (type == ResourceType.MPLS_LABEL) {
+ return getMplsResourceCapacity();
+ }
+ return ImmutableSet.of();
+ }
+
+ private Set<LambdaResourceAllocation> getLambdaResourceCapacity(Link link) {
+ Set<LambdaResourceAllocation> allocations = new HashSet<>();
+ Port port = deviceService.getPort(link.src().deviceId(), link.src().port());
+ if (port instanceof OmsPort) {
+ OmsPort omsPort = (OmsPort) port;
+
+ // Assume fixed grid for now
+ for (int i = 0; i < omsPort.totalChannels(); i++) {
+ allocations.add(new LambdaResourceAllocation(LambdaResource.valueOf(i)));
+ }
+ }
+ return allocations;
+ }
+
+ private BandwidthResourceAllocation getBandwidthResourceCapacity(Link link) {
+
+ // if Link annotation exist, use them
+ // if all fails, use DEFAULT_BANDWIDTH
+ BandwidthResource bandwidth = null;
+ String strBw = link.annotations().value(BANDWIDTH);
+ if (strBw != null) {
+ try {
+ bandwidth = new BandwidthResource(Bandwidth.mbps(Double.parseDouble(strBw)));
+ } catch (NumberFormatException e) {
+ // do nothings
+ bandwidth = null;
+ }
+ }
+
+ if (bandwidth == null) {
+ // fall back, use fixed default
+ bandwidth = DEFAULT_BANDWIDTH;
+ }
+ return new BandwidthResourceAllocation(bandwidth);
+ }
+
+ private Set<MplsLabelResourceAllocation> getMplsResourceCapacity() {
+ Set<MplsLabelResourceAllocation> allocations = new HashSet<>();
+ //Ignoring reserved labels of 0 through 15
+ for (int i = MIN_UNRESERVED_LABEL; i <= MAX_UNRESERVED_LABEL; i++) {
+ allocations.add(new MplsLabelResourceAllocation(MplsLabel
+ .valueOf(i)));
+
+ }
+ return allocations;
+ }
+
+ private Map<ResourceType, Set<? extends ResourceAllocation>> getResourceCapacity(Link link) {
+ Map<ResourceType, Set<? extends ResourceAllocation>> caps = new HashMap<>();
+ for (ResourceType type : ResourceType.values()) {
+ Set<? extends ResourceAllocation> cap = getResourceCapacity(type, link);
+ if (cap != null) {
+ caps.put(type, cap);
+ }
+ }
+ return caps;
+ }
+
+ @Override
+ public Set<ResourceAllocation> getFreeResources(Link link) {
+ TransactionContext tx = getTxContext();
+
+ tx.begin();
+ try {
+ Map<ResourceType, Set<? extends ResourceAllocation>> freeResources = getFreeResourcesEx(tx, link);
+ Set<ResourceAllocation> allFree = new HashSet<>();
+ freeResources.values().forEach(allFree::addAll);
+ return allFree;
+ } finally {
+ tx.abort();
+ }
+ }
+
+ private Map<ResourceType, Set<? extends ResourceAllocation>> getFreeResourcesEx(TransactionContext tx, Link link) {
+ checkNotNull(tx);
+ checkNotNull(link);
+
+ Map<ResourceType, Set<? extends ResourceAllocation>> free = new HashMap<>();
+ final Map<ResourceType, Set<? extends ResourceAllocation>> caps = getResourceCapacity(link);
+ final Iterable<LinkResourceAllocations> allocations = getAllocations(tx, link);
+
+ for (ResourceType type : ResourceType.values()) {
+ // there should be class/category of resources
+
+ switch (type) {
+ case BANDWIDTH:
+ Set<? extends ResourceAllocation> bw = caps.get(type);
+ if (bw == null || bw.isEmpty()) {
+ bw = Sets.newHashSet(new BandwidthResourceAllocation(EMPTY_BW));
+ }
+
+ BandwidthResourceAllocation cap = (BandwidthResourceAllocation) bw.iterator().next();
+ double freeBw = cap.bandwidth().toDouble();
+
+ // enumerate current allocations, subtracting resources
+ for (LinkResourceAllocations alloc : allocations) {
+ Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
+ for (ResourceAllocation a : types) {
+ if (a instanceof BandwidthResourceAllocation) {
+ BandwidthResourceAllocation bwA = (BandwidthResourceAllocation) a;
+ freeBw -= bwA.bandwidth().toDouble();
+ }
+ }
+ }
+
+ free.put(type, Sets.newHashSet(
+ new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(freeBw)))));
+ break;
+ case LAMBDA:
+ Set<? extends ResourceAllocation> lmd = caps.get(type);
+ if (lmd == null || lmd.isEmpty()) {
+ // nothing left
+ break;
+ }
+ Set<LambdaResourceAllocation> freeL = new HashSet<>();
+ for (ResourceAllocation r : lmd) {
+ if (r instanceof LambdaResourceAllocation) {
+ freeL.add((LambdaResourceAllocation) r);
+ }
+ }
+
+ // enumerate current allocations, removing resources
+ for (LinkResourceAllocations alloc : allocations) {
+ Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
+ for (ResourceAllocation a : types) {
+ if (a instanceof LambdaResourceAllocation) {
+ freeL.remove(a);
+ }
+ }
+ }
+
+ free.put(type, freeL);
+ break;
+ case MPLS_LABEL:
+ Set<? extends ResourceAllocation> mpls = caps.get(type);
+ if (mpls == null || mpls.isEmpty()) {
+ // nothing left
+ break;
+ }
+ Set<MplsLabelResourceAllocation> freeLabel = new HashSet<>();
+ for (ResourceAllocation r : mpls) {
+ if (r instanceof MplsLabelResourceAllocation) {
+ freeLabel.add((MplsLabelResourceAllocation) r);
+ }
+ }
+
+ // enumerate current allocations, removing resources
+ for (LinkResourceAllocations alloc : allocations) {
+ Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
+ for (ResourceAllocation a : types) {
+ if (a instanceof MplsLabelResourceAllocation) {
+ freeLabel.remove(a);
+ }
+ }
+ }
+
+ free.put(type, freeLabel);
+ break;
+ default:
+ log.debug("unsupported ResourceType {}", type);
+ break;
+ }
+ }
+ return free;
+ }
+
+ @Override
+ public void allocateResources(LinkResourceAllocations allocations) {
+ checkNotNull(allocations);
+ TransactionContext tx = getTxContext();
+
+ tx.begin();
+ try {
+ TransactionalMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
+ intentAllocs.put(allocations.intentId(), allocations);
+ allocations.links().forEach(link -> allocateLinkResource(tx, link, allocations));
+ tx.commit();
+ } catch (Exception e) {
+ log.error("Exception thrown, rolling back", e);
+ tx.abort();
+ throw e;
+ }
+ }
+
+ private void allocateLinkResource(TransactionContext tx, Link link,
+ LinkResourceAllocations allocations) {
+ // requested resources
+ Set<ResourceAllocation> reqs = allocations.getResourceAllocation(link);
+ Map<ResourceType, Set<? extends ResourceAllocation>> available = getFreeResourcesEx(tx, link);
+ for (ResourceAllocation req : reqs) {
+ Set<? extends ResourceAllocation> avail = available.get(req.type());
+ if (req instanceof BandwidthResourceAllocation) {
+ // check if allocation should be accepted
+ if (avail.isEmpty()) {
+ checkState(!avail.isEmpty(),
+ "There's no Bandwidth resource on %s?",
+ link);
+ }
+ BandwidthResourceAllocation bw = (BandwidthResourceAllocation) avail.iterator().next();
+ double bwLeft = bw.bandwidth().toDouble();
+ BandwidthResourceAllocation bwReq = ((BandwidthResourceAllocation) req);
+ bwLeft -= bwReq.bandwidth().toDouble();
+ if (bwLeft < 0) {
+ throw new ResourceAllocationException(
+ PositionalParameterStringFormatter.format(
+ "Unable to allocate bandwidth for link {} "
+ + " requested amount is {} current allocation is {}",
+ link,
+ bwReq.bandwidth().toDouble(),
+ bw));
+ }
+ } else if (req instanceof LambdaResourceAllocation) {
+ LambdaResourceAllocation lambdaAllocation = (LambdaResourceAllocation) req;
+ // check if allocation should be accepted
+ if (!avail.contains(req)) {
+ // requested lambda was not available
+ throw new ResourceAllocationException(
+ PositionalParameterStringFormatter.format(
+ "Unable to allocate lambda for link {} lambda is {}",
+ link,
+ lambdaAllocation.lambda().toInt()));
+ }
+ } else if (req instanceof MplsLabelResourceAllocation) {
+ MplsLabelResourceAllocation mplsAllocation = (MplsLabelResourceAllocation) req;
+ if (!avail.contains(req)) {
+ throw new ResourceAllocationException(
+ PositionalParameterStringFormatter
+ .format("Unable to allocate MPLS label for link "
+ + "{} MPLS label is {}",
+ link,
+ mplsAllocation
+ .mplsLabel()
+ .toString()));
+ }
+ }
+ }
+ // all requests allocatable => add allocation
+ final LinkKey linkKey = LinkKey.linkKey(link);
+ TransactionalMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
+ List<LinkResourceAllocations> before = linkAllocs.get(linkKey);
+ if (before == null) {
+ List<LinkResourceAllocations> after = new ArrayList<>();
+ after.add(allocations);
+ before = linkAllocs.putIfAbsent(linkKey, after);
+ if (before != null) {
+ // concurrent allocation detected, retry transaction : is this needed?
+ log.warn("Concurrent Allocation, retrying");
+ throw new TransactionException();
+ }
+ } else {
+ List<LinkResourceAllocations> after = new ArrayList<>(before.size() + 1);
+ after.addAll(before);
+ after.add(allocations);
+ linkAllocs.replace(linkKey, before, after);
+ }
+ }
+
+ @Override
+ public LinkResourceEvent releaseResources(LinkResourceAllocations allocations) {
+ checkNotNull(allocations);
+
+ final IntentId intentId = allocations.intentId();
+ final Collection<Link> links = allocations.links();
+ boolean success = false;
+ do {
+ TransactionContext tx = getTxContext();
+ tx.begin();
+ try {
+ TransactionalMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
+ intentAllocs.remove(intentId);
+
+ TransactionalMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
+ links.forEach(link -> {
+ final LinkKey linkId = LinkKey.linkKey(link);
+
+ List<LinkResourceAllocations> before = linkAllocs.get(linkId);
+ if (before == null || before.isEmpty()) {
+ // something is wrong, but it is already freed
+ log.warn("There was no resource left to release on {}", linkId);
+ return;
+ }
+ List<LinkResourceAllocations> after = new ArrayList<>(before);
+ after.remove(allocations);
+ linkAllocs.replace(linkId, before, after);
+ });
+ tx.commit();
+ success = true;
+ } catch (TransactionException e) {
+ log.debug("Transaction failed, retrying", e);
+ tx.abort();
+ } catch (Exception e) {
+ log.error("Exception thrown during releaseResource {}", allocations, e);
+ tx.abort();
+ throw e;
+ }
+ } while (!success);
+
+ // Issue events to force recompilation of intents.
+ final List<LinkResourceAllocations> releasedResources = ImmutableList.of(allocations);
+ return new LinkResourceEvent(
+ LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE,
+ releasedResources);
+
+ }
+
+ @Override
+ public LinkResourceAllocations getAllocations(IntentId intentId) {
+ checkNotNull(intentId);
+ Versioned<LinkResourceAllocations> alloc = null;
+ try {
+ alloc = intentAllocMap.get(intentId);
+ } catch (Exception e) {
+ log.warn("Could not read resource allocation information", e);
+ }
+ return alloc == null ? null : alloc.value();
+ }
+
+ @Override
+ public Iterable<LinkResourceAllocations> getAllocations(Link link) {
+ checkNotNull(link);
+ TransactionContext tx = getTxContext();
+ Iterable<LinkResourceAllocations> res = null;
+ tx.begin();
+ try {
+ res = getAllocations(tx, link);
+ } finally {
+ tx.abort();
+ }
+ return res == null ? Collections.emptyList() : res;
+ }
+
+ @Override
+ public Iterable<LinkResourceAllocations> getAllocations() {
+ try {
+ Set<LinkResourceAllocations> allocs =
+ intentAllocMap.values().stream().map(Versioned::value).collect(Collectors.toSet());
+ return ImmutableSet.copyOf(allocs);
+ } catch (Exception e) {
+ log.warn("Could not read resource allocation information", e);
+ }
+ return ImmutableSet.of();
+ }
+
+ private Iterable<LinkResourceAllocations> getAllocations(TransactionContext tx, Link link) {
+ checkNotNull(tx);
+ checkNotNull(link);
+ final LinkKey key = LinkKey.linkKey(link);
+ TransactionalMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
+ List<LinkResourceAllocations> res = null;
+
+ res = linkAllocs.get(key);
+ if (res == null) {
+ res = linkAllocs.putIfAbsent(key, new ArrayList<>());
+
+ if (res == null) {
+ return Collections.emptyList();
+ } else {
+ return res;
+ }
+ }
+ return res;
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/resource/impl/package-info.java
new file mode 100644
index 00000000..7c30018d
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of distributed packet store.
+ */
+package org.onosproject.store.resource.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/ClusterMessageSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/ClusterMessageSerializer.java
new file mode 100644
index 00000000..76bf7984
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/ClusterMessageSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.store.serializers.custom;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public final class ClusterMessageSerializer extends Serializer<ClusterMessage> {
+
+ /**
+ * Creates a serializer for {@link ClusterMessage}.
+ */
+ public ClusterMessageSerializer() {
+ // does not accept null
+ super(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, ClusterMessage message) {
+ kryo.writeClassAndObject(output, message.sender());
+ kryo.writeClassAndObject(output, message.subject());
+ output.writeInt(message.payload().length);
+ output.writeBytes(message.payload());
+ }
+
+ @Override
+ public ClusterMessage read(Kryo kryo, Input input,
+ Class<ClusterMessage> type) {
+ NodeId sender = (NodeId) kryo.readClassAndObject(input);
+ MessageSubject subject = (MessageSubject) kryo.readClassAndObject(input);
+ int payloadSize = input.readInt();
+ byte[] payload = input.readBytes(payloadSize);
+ return new ClusterMessage(sender, subject, payload);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/DistributedStoreSerializers.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/DistributedStoreSerializers.java
new file mode 100644
index 00000000..5465b9b4
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/DistributedStoreSerializers.java
@@ -0,0 +1,42 @@
+/*
+ * 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.store.serializers.custom;
+
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+import org.onosproject.store.impl.Timestamped;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onlab.util.KryoNamespace;
+
+public final class DistributedStoreSerializers {
+
+
+ public static final int STORE_CUSTOM_BEGIN = KryoNamespaces.BEGIN_USER_CUSTOM_ID + 10;
+
+ /**
+ * KryoNamespace which can serialize ON.lab misc classes.
+ */
+ public static final KryoNamespace STORE_COMMON = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .register(Timestamped.class)
+ .register(new MastershipBasedTimestampSerializer(), MastershipBasedTimestamp.class)
+ .register(WallClockTimestamp.class)
+ .build();
+
+ // avoid instantiation
+ private DistributedStoreSerializers() {}
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MastershipBasedTimestampSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MastershipBasedTimestampSerializer.java
new file mode 100644
index 00000000..eb1b2b55
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MastershipBasedTimestampSerializer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.store.serializers.custom;
+
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+// To be used if Timestamp ever needs to cross bundle boundary.
+/**
+ * Kryo Serializer for {@link MastershipBasedTimestamp}.
+ */
+public class MastershipBasedTimestampSerializer extends Serializer<MastershipBasedTimestamp> {
+
+ /**
+ * Creates a serializer for {@link MastershipBasedTimestamp}.
+ */
+ public MastershipBasedTimestampSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, MastershipBasedTimestamp object) {
+ output.writeLong(object.termNumber());
+ output.writeLong(object.sequenceNumber());
+ }
+
+ @Override
+ public MastershipBasedTimestamp read(Kryo kryo, Input input, Class<MastershipBasedTimestamp> type) {
+ final long term = input.readLong();
+ final long sequence = input.readLong();
+ return new MastershipBasedTimestamp(term, sequence);
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MessageSubjectSerializer.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MessageSubjectSerializer.java
new file mode 100644
index 00000000..7ddee1b9
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/MessageSubjectSerializer.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.store.serializers.custom;
+
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public final class MessageSubjectSerializer extends Serializer<MessageSubject> {
+
+ /**
+ * Creates a serializer for {@link MessageSubject}.
+ */
+ public MessageSubjectSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+
+ @Override
+ public void write(Kryo kryo, Output output, MessageSubject object) {
+ output.writeString(object.value());
+ }
+
+ @Override
+ public MessageSubject read(Kryo kryo, Input input,
+ Class<MessageSubject> type) {
+ return new MessageSubject(input.readString());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/package-info.java
new file mode 100644
index 00000000..5cd4bee6
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/serializers/custom/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.
+ */
+
+/**
+ * Cluster messaging and distributed store serializers.
+ */
+//FIXME what is the right name for this package?
+//FIXME can this be moved to onos-core-serializers?
+package org.onosproject.store.serializers.custom;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/DistributedStatisticStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/DistributedStatisticStore.java
new file mode 100644
index 00000000..d5434730
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/DistributedStatisticStore.java
@@ -0,0 +1,317 @@
+/*
+ * 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.store.statistic.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.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.statistic.StatisticStore;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.HashSet;
+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.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.store.statistic.impl.StatisticStoreMessageSubjects.GET_CURRENT;
+import static org.onosproject.store.statistic.impl.StatisticStoreMessageSubjects.GET_PREVIOUS;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Maintains statistics using RPC calls to collect stats from remote instances
+ * on demand.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedStatisticStore implements StatisticStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // TODO: Make configurable.
+ private static final int MESSAGE_HANDLER_THREAD_POOL_SIZE = 4;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ private Map<ConnectPoint, InternalStatisticRepresentation> representations =
+ new ConcurrentHashMap<>();
+
+ private Map<ConnectPoint, Set<FlowEntry>> previous =
+ new ConcurrentHashMap<>();
+
+ private Map<ConnectPoint, Set<FlowEntry>> current =
+ new ConcurrentHashMap<>();
+
+ protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ // register this store specific classes here
+ .build();
+ }
+ };;
+
+ private ExecutorService messageHandlingExecutor;
+
+ private static final long STATISTIC_STORE_TIMEOUT_MILLIS = 3000;
+
+ @Activate
+ public void activate() {
+
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ MESSAGE_HANDLER_THREAD_POOL_SIZE,
+ groupedThreads("onos/store/statistic", "message-handlers"));
+
+ clusterCommunicator.<ConnectPoint, Set<FlowEntry>>addSubscriber(GET_CURRENT,
+ SERIALIZER::decode,
+ this::getCurrentStatisticInternal,
+ SERIALIZER::encode,
+ messageHandlingExecutor);
+
+ clusterCommunicator.<ConnectPoint, Set<FlowEntry>>addSubscriber(GET_PREVIOUS,
+ SERIALIZER::decode,
+ this::getPreviousStatisticInternal,
+ SERIALIZER::encode,
+ messageHandlingExecutor);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ clusterCommunicator.removeSubscriber(GET_PREVIOUS);
+ clusterCommunicator.removeSubscriber(GET_CURRENT);
+ messageHandlingExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void prepareForStatistics(FlowRule rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep;
+ synchronized (representations) {
+ rep = getOrCreateRepresentation(cp);
+ }
+ rep.prepare();
+ }
+
+ @Override
+ public synchronized void removeFromStatistics(FlowRule rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep = representations.get(cp);
+ if (rep != null && rep.remove(rule)) {
+ updatePublishedStats(cp, Collections.emptySet());
+ }
+ Set<FlowEntry> values = current.get(cp);
+ if (values != null) {
+ values.remove(rule);
+ }
+ values = previous.get(cp);
+ if (values != null) {
+ values.remove(rule);
+ }
+
+ }
+
+ @Override
+ public void addOrUpdateStatistic(FlowEntry rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep = representations.get(cp);
+ if (rep != null && rep.submit(rule)) {
+ updatePublishedStats(cp, rep.get());
+ }
+ }
+
+ private synchronized void updatePublishedStats(ConnectPoint cp,
+ Set<FlowEntry> flowEntries) {
+ Set<FlowEntry> curr = current.get(cp);
+ if (curr == null) {
+ curr = new HashSet<>();
+ }
+ previous.put(cp, curr);
+ current.put(cp, flowEntries);
+
+ }
+
+ @Override
+ public Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) {
+ final DeviceId deviceId = connectPoint.deviceId();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ if (master == null) {
+ log.warn("No master for {}", deviceId);
+ return Collections.emptySet();
+ }
+ if (master.equals(clusterService.getLocalNode().id())) {
+ return getCurrentStatisticInternal(connectPoint);
+ } else {
+ return Tools.futureGetOrElse(clusterCommunicator.sendAndReceive(
+ connectPoint,
+ GET_CURRENT,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master),
+ STATISTIC_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ Collections.emptySet());
+ }
+
+ }
+
+ private synchronized Set<FlowEntry> getCurrentStatisticInternal(ConnectPoint connectPoint) {
+ return current.get(connectPoint);
+ }
+
+ @Override
+ public Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) {
+ final DeviceId deviceId = connectPoint.deviceId();
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ if (master == null) {
+ log.warn("No master for {}", deviceId);
+ return Collections.emptySet();
+ }
+ if (master.equals(clusterService.getLocalNode().id())) {
+ return getPreviousStatisticInternal(connectPoint);
+ } else {
+ return Tools.futureGetOrElse(clusterCommunicator.sendAndReceive(
+ connectPoint,
+ GET_PREVIOUS,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ master),
+ STATISTIC_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ Collections.emptySet());
+ }
+ }
+
+ private synchronized Set<FlowEntry> getPreviousStatisticInternal(ConnectPoint connectPoint) {
+ return previous.get(connectPoint);
+ }
+
+ private InternalStatisticRepresentation getOrCreateRepresentation(ConnectPoint cp) {
+
+ if (representations.containsKey(cp)) {
+ return representations.get(cp);
+ } else {
+ InternalStatisticRepresentation rep = new InternalStatisticRepresentation();
+ representations.put(cp, rep);
+ return rep;
+ }
+
+ }
+
+ private ConnectPoint buildConnectPoint(FlowRule rule) {
+ PortNumber port = getOutput(rule);
+
+ if (port == null) {
+ return null;
+ }
+ ConnectPoint cp = new ConnectPoint(rule.deviceId(), port);
+ return cp;
+ }
+
+ private PortNumber getOutput(FlowRule rule) {
+ for (Instruction i : rule.treatment().allInstructions()) {
+ if (i.type() == Instruction.Type.OUTPUT) {
+ Instructions.OutputInstruction out = (Instructions.OutputInstruction) i;
+ return out.port();
+ }
+ if (i.type() == Instruction.Type.DROP) {
+ return PortNumber.P0;
+ }
+ }
+ return null;
+ }
+
+ private class InternalStatisticRepresentation {
+
+ private final AtomicInteger counter = new AtomicInteger(0);
+ private final Set<FlowEntry> rules = new HashSet<>();
+
+ public void prepare() {
+ counter.incrementAndGet();
+ }
+
+ public synchronized boolean remove(FlowRule rule) {
+ rules.remove(rule);
+ return counter.decrementAndGet() == 0;
+ }
+
+ public synchronized boolean submit(FlowEntry rule) {
+ if (rules.contains(rule)) {
+ rules.remove(rule);
+ }
+ rules.add(rule);
+ if (counter.get() == 0) {
+ return true;
+ } else {
+ return counter.decrementAndGet() == 0;
+ }
+ }
+
+ public synchronized Set<FlowEntry> get() {
+ counter.set(rules.size());
+ return Sets.newHashSet(rules);
+ }
+
+
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/StatisticStoreMessageSubjects.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/StatisticStoreMessageSubjects.java
new file mode 100644
index 00000000..cc03c302
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/StatisticStoreMessageSubjects.java
@@ -0,0 +1,30 @@
+/*
+ * 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.store.statistic.impl;
+
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by DistributedStatisticStore peer-peer communication.
+ */
+public final class StatisticStoreMessageSubjects {
+ private StatisticStoreMessageSubjects() {}
+ public static final MessageSubject GET_CURRENT =
+ new MessageSubject("peer-return-current");
+ public static final MessageSubject GET_PREVIOUS =
+ new MessageSubject("peer-return-previous");
+
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/statistic/impl/package-info.java
new file mode 100644
index 00000000..49436a9f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of the statistic store.
+ */
+package org.onosproject.store.statistic.impl;
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java
new file mode 100644
index 00000000..487fad9b
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java
@@ -0,0 +1,254 @@
+/*
+ * 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.store.topology.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onlab.util.Tools.isNullOrEmpty;
+import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+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.KryoNamespace;
+import org.onosproject.common.DefaultTopology;
+import org.onosproject.event.Event;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.ClusterId;
+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.TopologyStore;
+import org.onosproject.net.topology.TopologyStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.LogicalClockService;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+/**
+ * Manages inventory of topology snapshots using trivial in-memory
+ * structures implementation.
+ * <p>
+ * Note: This component is not distributed per-se. It runs on every
+ * instance and feeds off of other distributed stores.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedTopologyStore
+ extends AbstractStore<TopologyEvent, TopologyStoreDelegate>
+ implements TopologyStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private volatile DefaultTopology current =
+ new DefaultTopology(ProviderId.NONE,
+ new DefaultGraphDescription(0L, System.currentTimeMillis(),
+ Collections.<Device>emptyList(),
+ Collections.<Link>emptyList()));
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LogicalClockService clockService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ // Cluster root to broadcast points bindings to allow convergence to
+ // a shared broadcast tree; node that is the master of the cluster root
+ // is the primary.
+ private EventuallyConsistentMap<DeviceId, Set<ConnectPoint>> broadcastPoints;
+
+ private EventuallyConsistentMapListener<DeviceId, Set<ConnectPoint>> listener =
+ new InternalBroadcastPointListener();
+
+ @Activate
+ public void activate() {
+ KryoNamespace.Builder hostSerializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API);
+
+ broadcastPoints = storageService.<DeviceId, Set<ConnectPoint>>eventuallyConsistentMapBuilder()
+ .withName("onos-broadcast-trees")
+ .withSerializer(hostSerializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp())
+ .build();
+ broadcastPoints.addListener(listener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ broadcastPoints.removeListener(listener);
+ broadcastPoints.destroy();
+ log.info("Stopped");
+ }
+
+ @Override
+ public Topology currentTopology() {
+ return current;
+ }
+
+ @Override
+ public boolean isLatest(Topology topology) {
+ // Topology is current only if it is the same as our current topology
+ return topology == current;
+ }
+
+ @Override
+ public TopologyGraph getGraph(Topology topology) {
+ return defaultTopology(topology).getGraph();
+ }
+
+ @Override
+ public Set<TopologyCluster> getClusters(Topology topology) {
+ return defaultTopology(topology).getClusters();
+ }
+
+ @Override
+ public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
+ return defaultTopology(topology).getCluster(clusterId);
+ }
+
+ @Override
+ public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
+ return defaultTopology(topology).getClusterDevices(cluster);
+ }
+
+ @Override
+ public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
+ return defaultTopology(topology).getClusterLinks(cluster);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+ return defaultTopology(topology).getPaths(src, dst);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+ LinkWeight weight) {
+ return defaultTopology(topology).getPaths(src, dst, weight);
+ }
+
+ @Override
+ public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
+ return defaultTopology(topology).isInfrastructure(connectPoint);
+ }
+
+ @Override
+ public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
+ return defaultTopology(topology).isBroadcastPoint(connectPoint);
+ }
+
+ private boolean isBroadcastPoint(ConnectPoint connectPoint) {
+ // Any non-infrastructure, i.e. edge points are assumed to be OK.
+ if (!current.isInfrastructure(connectPoint)) {
+ return true;
+ }
+
+ // Find the cluster to which the device belongs.
+ TopologyCluster cluster = current.getCluster(connectPoint.deviceId());
+ checkArgument(cluster != null, "No cluster found for device %s", connectPoint.deviceId());
+
+ // If the broadcast set is null or empty, or if the point explicitly
+ // belongs to it, return true;
+ Set<ConnectPoint> points = broadcastPoints.get(cluster.root().deviceId());
+ return isNullOrEmpty(points) || points.contains(connectPoint);
+ }
+
+ @Override
+ public TopologyEvent updateTopology(ProviderId providerId,
+ GraphDescription graphDescription,
+ List<Event> reasons) {
+ // First off, make sure that what we're given is indeed newer than
+ // what we already have.
+ if (current != null && graphDescription.timestamp() < current.time()) {
+ return null;
+ }
+
+ // Have the default topology construct self from the description data.
+ DefaultTopology newTopology =
+ new DefaultTopology(providerId, graphDescription, this::isBroadcastPoint);
+ updateBroadcastPoints(newTopology);
+
+ // Promote the new topology to current and return a ready-to-send event.
+ synchronized (this) {
+ current = newTopology;
+ return new TopologyEvent(TOPOLOGY_CHANGED, current, reasons);
+ }
+ }
+
+ private void updateBroadcastPoints(DefaultTopology topology) {
+ // Remove any broadcast trees rooted by devices for which we are master.
+ Set<DeviceId> toRemove = broadcastPoints.keySet().stream()
+ .filter(mastershipService::isLocalMaster)
+ .collect(Collectors.toSet());
+
+ // Update the broadcast trees rooted by devices for which we are master.
+ topology.getClusters().forEach(c -> {
+ toRemove.remove(c.root().deviceId());
+ if (mastershipService.isLocalMaster(c.root().deviceId())) {
+ broadcastPoints.put(c.root().deviceId(),
+ topology.broadcastPoints(c.id()));
+ }
+ });
+
+ toRemove.forEach(broadcastPoints::remove);
+ }
+
+ // Validates the specified topology and returns it as a default
+ private DefaultTopology defaultTopology(Topology topology) {
+ checkArgument(topology instanceof DefaultTopology,
+ "Topology class %s not supported", topology.getClass());
+ return (DefaultTopology) topology;
+ }
+
+ private class InternalBroadcastPointListener
+ implements EventuallyConsistentMapListener<DeviceId, Set<ConnectPoint>> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<DeviceId, Set<ConnectPoint>> event) {
+ if (event.type() == EventuallyConsistentMapEvent.Type.PUT) {
+ if (!event.value().isEmpty()) {
+ log.info("Cluster rooted at {} has {} broadcast-points; #{}",
+ event.key(), event.value().size(), event.value().hashCode());
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/PathKey.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/PathKey.java
new file mode 100644
index 00000000..f1c2bdc5
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/PathKey.java
@@ -0,0 +1,55 @@
+/*
+ * 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.store.topology.impl;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * Key for filing pre-computed paths between source and destination devices.
+ */
+class PathKey {
+ private final DeviceId src;
+ private final DeviceId dst;
+
+ /**
+ * Creates a path key from the given source/dest pair.
+ * @param src source device
+ * @param dst destination device
+ */
+ PathKey(DeviceId src, DeviceId dst) {
+ this.src = src;
+ this.dst = dst;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(src, dst);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PathKey) {
+ final PathKey other = (PathKey) obj;
+ return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
+ }
+ return false;
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/package-info.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/topology/impl/package-info.java
new file mode 100644
index 00000000..d1590793
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/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.
+ */
+
+/**
+ * Implementation of distributed topology store using p2p synchronization protocol.
+ */
+package org.onosproject.store.topology.impl;
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/StaticClusterService.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/StaticClusterService.java
new file mode 100644
index 00000000..faaf2978
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/StaticClusterService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.store.cluster;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.ControllerNode.State;
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.collect.Sets;
+
+public abstract class StaticClusterService extends ClusterServiceAdapter {
+
+ protected final Map<NodeId, ControllerNode> nodes = new HashMap<>();
+ protected final Map<NodeId, ControllerNode.State> nodeStates = new HashMap<>();
+ protected ControllerNode localNode;
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return localNode;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return Sets.newHashSet(nodes.values());
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ return nodes.get(nodeId);
+ }
+
+ @Override
+ public State getState(NodeId nodeId) {
+ return nodeStates.get(nodeId);
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManagerTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManagerTest.java
new file mode 100644
index 00000000..0dcc6a10
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/ClusterCommunicationManagerTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.store.cluster.messaging.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.cluster.impl.ClusterNodesDelegate;
+import org.onlab.packet.IpAddress;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests of the cluster communication manager.
+ */
+public class ClusterCommunicationManagerTest {
+
+ private static final NodeId N1 = new NodeId("n1");
+ private static final NodeId N2 = new NodeId("n2");
+
+ private static final int P1 = 9881;
+ private static final int P2 = 9882;
+
+ private static final IpAddress IP = IpAddress.valueOf("127.0.0.1");
+
+ private ClusterCommunicationManager ccm1;
+ private ClusterCommunicationManager ccm2;
+
+ private TestDelegate cnd1 = new TestDelegate();
+ private TestDelegate cnd2 = new TestDelegate();
+
+ private DefaultControllerNode node1 = new DefaultControllerNode(N1, IP, P1);
+ private DefaultControllerNode node2 = new DefaultControllerNode(N2, IP, P2);
+
+ @Before
+ public void setUp() throws Exception {
+
+ NettyMessagingManager messagingService = new NettyMessagingManager();
+ messagingService.activate();
+
+ ccm1 = new ClusterCommunicationManager();
+ ccm1.activate();
+
+ ccm2 = new ClusterCommunicationManager();
+ ccm2.activate();
+
+// ccm1.initialize(node1, cnd1);
+// ccm2.initialize(node2, cnd2);
+ }
+
+ @After
+ public void tearDown() {
+ ccm1.deactivate();
+ ccm2.deactivate();
+ }
+
+ @Ignore("FIXME: failing randomly?")
+ @Test
+ public void connect() throws Exception {
+ cnd1.latch = new CountDownLatch(1);
+ cnd2.latch = new CountDownLatch(1);
+
+// ccm1.addNode(node2);
+ validateDelegateEvent(cnd1, Op.DETECTED, node2.id());
+ validateDelegateEvent(cnd2, Op.DETECTED, node1.id());
+ }
+
+ @Test
+ @Ignore
+ public void disconnect() throws Exception {
+ cnd1.latch = new CountDownLatch(1);
+ cnd2.latch = new CountDownLatch(1);
+
+// ccm1.addNode(node2);
+ validateDelegateEvent(cnd1, Op.DETECTED, node2.id());
+ validateDelegateEvent(cnd2, Op.DETECTED, node1.id());
+
+ cnd1.latch = new CountDownLatch(1);
+ cnd2.latch = new CountDownLatch(1);
+ ccm1.deactivate();
+//
+// validateDelegateEvent(cnd2, Op.VANISHED, node1.id());
+ }
+
+ private void validateDelegateEvent(TestDelegate delegate, Op op, NodeId nodeId)
+ throws InterruptedException {
+ assertTrue("did not connect in time", delegate.latch.await(2500, TimeUnit.MILLISECONDS));
+ assertEquals("incorrect event", op, delegate.op);
+ assertEquals("incorrect event node", nodeId, delegate.nodeId);
+ }
+
+ enum Op { DETECTED, VANISHED, REMOVED };
+
+ private class TestDelegate implements ClusterNodesDelegate {
+
+ Op op;
+ CountDownLatch latch;
+ NodeId nodeId;
+
+ @Override
+ public DefaultControllerNode nodeDetected(NodeId nodeId, IpAddress ip, int tcpPort) {
+ latch(nodeId, Op.DETECTED);
+ return new DefaultControllerNode(nodeId, ip, tcpPort);
+ }
+
+ @Override
+ public void nodeVanished(NodeId nodeId) {
+ latch(nodeId, Op.VANISHED);
+ }
+
+ @Override
+ public void nodeRemoved(NodeId nodeId) {
+ latch(nodeId, Op.REMOVED);
+ }
+
+ private void latch(NodeId nodeId, Op op) {
+ this.op = op;
+ this.nodeId = nodeId;
+ latch.countDown();
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/config/impl/DistributedNetworkConfigStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/config/impl/DistributedNetworkConfigStoreTest.java
new file mode 100644
index 00000000..06fe7b37
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/config/impl/DistributedNetworkConfigStoreTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.store.config.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.SubjectFactory;
+import org.onosproject.store.service.TestStorageService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class DistributedNetworkConfigStoreTest {
+ private DistributedNetworkConfigStore configStore;
+
+ /**
+ * Sets up the config store and the storage service test harness.
+ */
+ @Before
+ public void setUp() {
+ configStore = new DistributedNetworkConfigStore();
+ configStore.storageService = new TestStorageService();
+ configStore.setDelegate(event -> { });
+ configStore.activate();
+ }
+
+ /**
+ * Tears down the config store.
+ */
+ @After
+ public void tearDown() {
+ configStore.deactivate();
+ }
+
+ /**
+ * Config class for testing.
+ */
+ public class BasicConfig extends Config<String> { }
+
+ /**
+ * Config factory class for testing.
+ */
+ public class MockConfigFactory extends ConfigFactory<String, BasicConfig> {
+ protected MockConfigFactory(SubjectFactory<String> subjectFactory,
+ Class<BasicConfig> configClass, String configKey) {
+ super(subjectFactory, configClass, configKey);
+ }
+ @Override
+ public BasicConfig createConfig() {
+ return new BasicConfig();
+ }
+ }
+
+ /**
+ * Tests creation, query and removal of a config.
+ */
+ @Test
+ public void testCreateConfig() {
+ configStore.addConfigFactory(new MockConfigFactory(null, BasicConfig.class, "config1"));
+
+ configStore.createConfig("config1", BasicConfig.class);
+ assertThat(configStore.getConfigClasses("config1"), hasSize(1));
+ assertThat(configStore.getSubjects(String.class, BasicConfig.class), hasSize(1));
+ assertThat(configStore.getSubjects(String.class), hasSize(1));
+
+ BasicConfig queried = configStore.getConfig("config1", BasicConfig.class);
+ assertThat(queried, notNullValue());
+
+ configStore.clearConfig("config1", BasicConfig.class);
+ assertThat(configStore.getConfigClasses("config1"), hasSize(0));
+ assertThat(configStore.getSubjects(String.class, BasicConfig.class), hasSize(0));
+ assertThat(configStore.getSubjects(String.class), hasSize(0));
+
+ BasicConfig queriedAfterClear = configStore.getConfig("config1", BasicConfig.class);
+ assertThat(queriedAfterClear, nullValue());
+ }
+
+ /**
+ * Tests creation, query and removal of a factory.
+ */
+ @Test
+ public void testCreateFactory() {
+ MockConfigFactory mockFactory = new MockConfigFactory(null, BasicConfig.class, "config1");
+
+ assertThat(configStore.getConfigFactory(BasicConfig.class), nullValue());
+
+ configStore.addConfigFactory(mockFactory);
+ assertThat(configStore.getConfigFactory(BasicConfig.class), is(mockFactory));
+
+ configStore.removeConfigFactory(mockFactory);
+ assertThat(configStore.getConfigFactory(BasicConfig.class), nullValue());
+ }
+
+ /**
+ * Tests applying a config.
+ */
+ @Test
+ public void testApplyConfig() {
+ configStore.addConfigFactory(new MockConfigFactory(null, BasicConfig.class, "config1"));
+
+ configStore.applyConfig("config1", BasicConfig.class, new ObjectMapper().createObjectNode());
+ assertThat(configStore.getConfigClasses("config1"), hasSize(1));
+ assertThat(configStore.getSubjects(String.class, BasicConfig.class), hasSize(1));
+ assertThat(configStore.getSubjects(String.class), hasSize(1));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/MatchTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/MatchTest.java
new file mode 100644
index 00000000..7ff94c88
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/MatchTest.java
@@ -0,0 +1,52 @@
+package org.onosproject.store.consistent.impl;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import org.junit.Test;
+
+import com.google.common.base.Objects;
+
+/**
+ * Unit tests for Match.
+ */
+public class MatchTest {
+
+ @Test
+ public void testMatches() {
+ Match<String> m1 = Match.any();
+ assertTrue(m1.matches(null));
+ assertTrue(m1.matches("foo"));
+ assertTrue(m1.matches("bar"));
+
+ Match<String> m2 = Match.ifNull();
+ assertTrue(m2.matches(null));
+ assertFalse(m2.matches("foo"));
+
+ Match<String> m3 = Match.ifValue("foo");
+ assertFalse(m3.matches(null));
+ assertFalse(m3.matches("bar"));
+ assertTrue(m3.matches("foo"));
+ }
+
+ @Test
+ public void testEquals() {
+ Match<String> m1 = Match.any();
+ Match<String> m2 = Match.any();
+ Match<String> m3 = Match.ifNull();
+ Match<String> m4 = Match.ifValue("bar");
+ assertEquals(m1, m2);
+ assertFalse(Objects.equal(m1, m3));
+ assertFalse(Objects.equal(m3, m4));
+ }
+
+ @Test
+ public void testMap() {
+ Match<String> m1 = Match.ifNull();
+ assertEquals(m1.map(s -> "bar"), Match.ifNull());
+ Match<String> m2 = Match.ifValue("foo");
+ Match<String> m3 = m2.map(s -> "bar");
+ assertTrue(m3.matches("bar"));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/ResultTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/ResultTest.java
new file mode 100644
index 00000000..2a3bab87
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/ResultTest.java
@@ -0,0 +1,42 @@
+package org.onosproject.store.consistent.impl;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for Result.
+ */
+public class ResultTest {
+
+ @Test
+ public void testLocked() {
+ Result<String> r = Result.locked();
+ assertFalse(r.success());
+ assertNull(r.value());
+ assertEquals(Result.Status.LOCKED, r.status());
+ }
+
+ @Test
+ public void testOk() {
+ Result<String> r = Result.ok("foo");
+ assertTrue(r.success());
+ assertEquals("foo", r.value());
+ assertEquals(Result.Status.OK, r.status());
+ }
+
+ @Test
+ public void testEquality() {
+ Result<String> r1 = Result.ok("foo");
+ Result<String> r2 = Result.locked();
+ Result<String> r3 = Result.ok("bar");
+ Result<String> r4 = Result.ok("foo");
+ assertTrue(r1.equals(r4));
+ assertFalse(r1.equals(r2));
+ assertFalse(r1.equals(r3));
+ assertFalse(r2.equals(r3));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/UpdateResultTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/UpdateResultTest.java
new file mode 100644
index 00000000..84dc9153
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/consistent/impl/UpdateResultTest.java
@@ -0,0 +1,84 @@
+package org.onosproject.store.consistent.impl;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+
+import org.junit.Test;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.Versioned;
+
+/**
+ * Unit tests for UpdateResult.
+ */
+public class UpdateResultTest {
+
+ @Test
+ public void testGetters() {
+ Versioned<String> oldValue = new Versioned<String>("a", 1);
+ Versioned<String> newValue = new Versioned<String>("b", 2);
+ UpdateResult<String, String> ur =
+ new UpdateResult<>(true, "foo", "k", oldValue, newValue);
+
+ assertTrue(ur.updated());
+ assertEquals("foo", ur.mapName());
+ assertEquals("k", ur.key());
+ assertEquals(oldValue, ur.oldValue());
+ assertEquals(newValue, ur.newValue());
+ }
+
+ @Test
+ public void testToMapEvent() {
+ Versioned<String> oldValue = new Versioned<String>("a", 1);
+ Versioned<String> newValue = new Versioned<String>("b", 2);
+ UpdateResult<String, String> ur1 =
+ new UpdateResult<>(true, "foo", "k", oldValue, newValue);
+ MapEvent<String, String> event1 = ur1.toMapEvent();
+ assertEquals(MapEvent.Type.UPDATE, event1.type());
+ assertEquals("k", event1.key());
+ assertEquals(newValue, event1.value());
+
+ UpdateResult<String, String> ur2 =
+ new UpdateResult<>(true, "foo", "k", null, newValue);
+ MapEvent<String, String> event2 = ur2.toMapEvent();
+ assertEquals(MapEvent.Type.INSERT, event2.type());
+ assertEquals("k", event2.key());
+ assertEquals(newValue, event2.value());
+
+ UpdateResult<String, String> ur3 =
+ new UpdateResult<>(true, "foo", "k", oldValue, null);
+ MapEvent<String, String> event3 = ur3.toMapEvent();
+ assertEquals(MapEvent.Type.REMOVE, event3.type());
+ assertEquals("k", event3.key());
+ assertEquals(oldValue, event3.value());
+
+ UpdateResult<String, String> ur4 =
+ new UpdateResult<>(false, "foo", "k", oldValue, oldValue);
+ assertNull(ur4.toMapEvent());
+ }
+
+ @Test
+ public void testMap() {
+ Versioned<String> oldValue = new Versioned<String>("a", 1);
+ Versioned<String> newValue = new Versioned<String>("b", 2);
+ UpdateResult<String, String> ur1 =
+ new UpdateResult<>(true, "foo", "k", oldValue, newValue);
+ UpdateResult<Integer, Integer> ur2 = ur1.map(s -> s.length(), s -> s.length());
+
+ assertEquals(ur2.updated(), ur1.updated());
+ assertEquals(ur1.mapName(), ur2.mapName());
+ assertEquals(new Integer(1), ur2.key());
+ assertEquals(oldValue.map(s -> s.length()), ur2.oldValue());
+ assertEquals(newValue.map(s -> s.length()), ur2.newValue());
+
+ UpdateResult<String, String> ur3 =
+ new UpdateResult<>(true, "foo", "k", null, newValue);
+ UpdateResult<Integer, Integer> ur4 = ur3.map(s -> s.length(), s -> s.length());
+
+ assertEquals(ur3.updated(), ur4.updated());
+ assertEquals(ur3.mapName(), ur4.mapName());
+ assertEquals(new Integer(1), ur4.key());
+ assertNull(ur4.oldValue());
+ assertEquals(newValue.map(s -> s.length()), ur4.newValue());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/DeviceFragmentIdTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/DeviceFragmentIdTest.java
new file mode 100644
index 00000000..191b3bea
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/DeviceFragmentIdTest.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.store.device.impl;
+
+import static org.onosproject.net.DeviceId.deviceId;
+
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.testing.EqualsTester;
+
+public class DeviceFragmentIdTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+
+ @Test
+ public final void testEquals() {
+
+ new EqualsTester()
+ .addEqualityGroup(new DeviceFragmentId(DID1, PID),
+ new DeviceFragmentId(DID1, PID))
+ .addEqualityGroup(new DeviceFragmentId(DID2, PID),
+ new DeviceFragmentId(DID2, PID))
+ .addEqualityGroup(new DeviceFragmentId(DID1, PIDA),
+ new DeviceFragmentId(DID1, PIDA))
+ .addEqualityGroup(new DeviceFragmentId(DID2, PIDA),
+ new DeviceFragmentId(DID2, PIDA))
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/GossipDeviceStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/GossipDeviceStoreTest.java
new file mode 100644
index 00000000..43b11f52
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/GossipDeviceStoreTest.java
@@ -0,0 +1,917 @@
+/*
+ * 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.store.device.impl;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.easymock.Capture;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+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.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.device.DeviceClockServiceAdapter;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceStore;
+import org.onosproject.net.device.DeviceStoreDelegate;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.StaticClusterService;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.consistent.impl.DatabaseManager;
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+
+import static java.util.Arrays.asList;
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
+import static org.onosproject.net.DefaultAnnotations.union;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+
+
+// TODO add tests for remote replication
+/**
+ * Test of the gossip based distributed DeviceStore implementation.
+ */
+public class GossipDeviceStoreTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+ 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 SparseAnnotations A1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .build();
+ private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+ .remove("A1")
+ .set("B3", "b3")
+ .build();
+ private static final SparseAnnotations A2 = DefaultAnnotations.builder()
+ .set("A2", "a2")
+ .set("B2", "b2")
+ .build();
+ private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
+ .remove("A2")
+ .set("B4", "b4")
+ .build();
+
+ // local node
+ private static final NodeId NID1 = new NodeId("local");
+ private static final ControllerNode ONOS1 =
+ new DefaultControllerNode(NID1, IpAddress.valueOf("127.0.0.1"));
+
+ // remote node
+ private static final NodeId NID2 = new NodeId("remote");
+ private static final ControllerNode ONOS2 =
+ new DefaultControllerNode(NID2, IpAddress.valueOf("127.0.0.2"));
+ private static final List<SparseAnnotations> NO_ANNOTATION = Collections.<SparseAnnotations>emptyList();
+
+
+ private TestGossipDeviceStore testGossipDeviceStore;
+ private GossipDeviceStore gossipDeviceStore;
+ private DeviceStore deviceStore;
+
+ private DeviceClockService deviceClockService = new TestDeviceClockService();
+ private ClusterCommunicationService clusterCommunicator;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ clusterCommunicator = createNiceMock(ClusterCommunicationService.class);
+ clusterCommunicator.addSubscriber(anyObject(MessageSubject.class),
+ anyObject(ClusterMessageHandler.class), anyObject(ExecutorService.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+ ClusterService clusterService = new TestClusterService();
+
+ testGossipDeviceStore = new TestGossipDeviceStore(deviceClockService, clusterService, clusterCommunicator);
+ testGossipDeviceStore.mastershipService = new TestMastershipService();
+
+ TestDatabaseManager testDatabaseManager = new TestDatabaseManager();
+ testDatabaseManager.init(clusterService, clusterCommunicator);
+ testGossipDeviceStore.storageService = testDatabaseManager;
+ testGossipDeviceStore.deviceClockService = deviceClockService;
+
+ gossipDeviceStore = testGossipDeviceStore;
+ gossipDeviceStore.activate();
+ deviceStore = gossipDeviceStore;
+ verify(clusterCommunicator);
+ reset(clusterCommunicator);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ gossipDeviceStore.deactivate();
+ }
+
+ private void putDevice(DeviceId deviceId, String swVersion,
+ SparseAnnotations... annotations) {
+ DeviceDescription description =
+ new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+ HW, swVersion, SN, CID, annotations);
+ reset(clusterCommunicator);
+ clusterCommunicator.<InternalDeviceEvent>broadcast(
+ anyObject(InternalDeviceEvent.class), anyObject(MessageSubject.class), anyObject(Function.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+ deviceStore.createOrUpdateDevice(PID, deviceId, description);
+ verify(clusterCommunicator);
+ }
+
+ private void putDeviceAncillary(DeviceId deviceId, String swVersion,
+ SparseAnnotations... annotations) {
+ DeviceDescription description =
+ new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+ HW, swVersion, SN, CID, annotations);
+ deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
+ }
+
+ private static void assertDevice(DeviceId id, String swVersion, Device device) {
+ assertNotNull(device);
+ assertEquals(id, device.id());
+ assertEquals(MFR, device.manufacturer());
+ assertEquals(HW, device.hwVersion());
+ assertEquals(swVersion, device.swVersion());
+ assertEquals(SN, device.serialNumber());
+ }
+
+ /**
+ * Verifies that Annotations created by merging {@code annotations} is
+ * equal to actual Annotations.
+ *
+ * @param actual Annotations to check
+ * @param annotations
+ */
+ private static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+ SparseAnnotations expected = DefaultAnnotations.builder().build();
+ for (SparseAnnotations a : annotations) {
+ expected = DefaultAnnotations.union(expected, a);
+ }
+ assertEquals(expected.keys(), actual.keys());
+ for (String key : expected.keys()) {
+ assertEquals(expected.value(key), actual.value(key));
+ }
+ }
+
+ private static void assertDeviceDescriptionEquals(DeviceDescription expected,
+ DeviceDescription actual) {
+ if (expected == actual) {
+ return;
+ }
+ assertEquals(expected.deviceURI(), actual.deviceURI());
+ assertEquals(expected.hwVersion(), actual.hwVersion());
+ assertEquals(expected.manufacturer(), actual.manufacturer());
+ assertEquals(expected.serialNumber(), actual.serialNumber());
+ assertEquals(expected.swVersion(), actual.swVersion());
+
+ assertAnnotationsEquals(actual.annotations(), expected.annotations());
+ }
+
+ private static void assertDeviceDescriptionEquals(DeviceDescription expected,
+ List<SparseAnnotations> expectedAnnotations,
+ DeviceDescription actual) {
+ if (expected == actual) {
+ return;
+ }
+ assertEquals(expected.deviceURI(), actual.deviceURI());
+ assertEquals(expected.hwVersion(), actual.hwVersion());
+ assertEquals(expected.manufacturer(), actual.manufacturer());
+ assertEquals(expected.serialNumber(), actual.serialNumber());
+ assertEquals(expected.swVersion(), actual.swVersion());
+
+ assertAnnotationsEquals(actual.annotations(),
+ expectedAnnotations.toArray(new SparseAnnotations[0]));
+ }
+
+ @Test
+ public final void testGetDeviceCount() {
+ assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
+
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW2);
+ putDevice(DID1, SW1);
+
+ assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
+ }
+
+ @Test
+ public final void testGetDevices() {
+ assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
+
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW2);
+ putDevice(DID1, SW1);
+
+ assertEquals("expect 2 uniq devices",
+ 2, Iterables.size(deviceStore.getDevices()));
+
+ Map<DeviceId, Device> devices = new HashMap<>();
+ for (Device device : deviceStore.getDevices()) {
+ devices.put(device.id(), device);
+ }
+
+ assertDevice(DID1, SW1, devices.get(DID1));
+ assertDevice(DID2, SW2, devices.get(DID2));
+
+ // add case for new node?
+ }
+
+ @Test
+ public final void testGetDevice() {
+
+ putDevice(DID1, SW1);
+
+ assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
+ assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
+ }
+
+ private void assertInternalDeviceEvent(NodeId sender,
+ DeviceId deviceId,
+ ProviderId providerId,
+ DeviceDescription expectedDesc,
+ Capture<InternalDeviceEvent> actualEvent,
+ Capture<MessageSubject> actualSubject,
+ Capture<Function<InternalDeviceEvent, byte[]>> actualEncoder) {
+ assertTrue(actualEvent.hasCaptured());
+ assertTrue(actualSubject.hasCaptured());
+ assertTrue(actualEncoder.hasCaptured());
+
+ assertEquals(GossipDeviceStoreMessageSubjects.DEVICE_UPDATE,
+ actualSubject.getValue());
+ assertEquals(deviceId, actualEvent.getValue().deviceId());
+ assertEquals(providerId, actualEvent.getValue().providerId());
+ assertDeviceDescriptionEquals(expectedDesc, actualEvent.getValue().deviceDescription().value());
+ }
+
+ private void assertInternalDeviceEvent(NodeId sender,
+ DeviceId deviceId,
+ ProviderId providerId,
+ DeviceDescription expectedDesc,
+ List<SparseAnnotations> expectedAnnotations,
+ Capture<InternalDeviceEvent> actualEvent,
+ Capture<MessageSubject> actualSubject,
+ Capture<Function<InternalDeviceEvent, byte[]>> actualEncoder) {
+ assertTrue(actualEvent.hasCaptured());
+ assertTrue(actualSubject.hasCaptured());
+ assertTrue(actualEncoder.hasCaptured());
+
+ assertEquals(GossipDeviceStoreMessageSubjects.DEVICE_UPDATE,
+ actualSubject.getValue());
+ assertEquals(deviceId, actualEvent.getValue().deviceId());
+ assertEquals(providerId, actualEvent.getValue().providerId());
+ assertDeviceDescriptionEquals(
+ expectedDesc,
+ expectedAnnotations,
+ actualEvent.getValue().deviceDescription().value());
+ }
+
+ @Test
+ public final void testCreateOrUpdateDevice() throws IOException {
+ DeviceDescription description =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID);
+ Capture<InternalDeviceEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalDeviceEvent, byte[]>> encoder = new Capture<>();
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
+ assertEquals(DEVICE_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ verify(clusterCommunicator);
+ assertInternalDeviceEvent(NID1, DID1, PID, description, message, subject, encoder);
+
+
+ DeviceDescription description2 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW2, SN, CID);
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+ assertEquals(DEVICE_UPDATED, event2.type());
+ assertDevice(DID1, SW2, event2.subject());
+
+ verify(clusterCommunicator);
+ assertInternalDeviceEvent(NID1, DID1, PID, description2, message, subject, encoder);
+ reset(clusterCommunicator);
+
+ assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+ }
+
+ @Test
+ public final void testCreateOrUpdateDeviceAncillary() throws IOException {
+ // add
+ DeviceDescription description =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID, A2);
+ Capture<ClusterMessage> bcast = new Capture<>();
+
+ Capture<InternalDeviceEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalDeviceEvent, byte[]>> encoder = new Capture<>();
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description);
+ assertEquals(DEVICE_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertEquals(PIDA, event.subject().providerId());
+ assertAnnotationsEquals(event.subject().annotations(), A2);
+ assertFalse("Ancillary will not bring device up", deviceStore.isAvailable(DID1));
+ verify(clusterCommunicator);
+ assertInternalDeviceEvent(NID1, DID1, PIDA, description, message, subject, encoder);
+
+ // update from primary
+ DeviceDescription description2 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW2, SN, CID, A1);
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+
+ DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+ assertEquals(DEVICE_UPDATED, event2.type());
+ assertDevice(DID1, SW2, event2.subject());
+ assertEquals(PID, event2.subject().providerId());
+ assertAnnotationsEquals(event2.subject().annotations(), A1, A2);
+ assertTrue(deviceStore.isAvailable(DID1));
+ verify(clusterCommunicator);
+ assertInternalDeviceEvent(NID1, DID1, PID, description2, message, subject, encoder);
+
+ // no-op update from primary
+ resetCommunicatorExpectingNoBroadcast(message, subject, encoder);
+ assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+
+ verify(clusterCommunicator);
+ assertFalse("no broadcast expected", bcast.hasCaptured());
+
+ // For now, Ancillary is ignored once primary appears
+ resetCommunicatorExpectingNoBroadcast(message, subject, encoder);
+
+ assertNull("No change expected", deviceStore.createOrUpdateDevice(PIDA, DID1, description));
+
+ verify(clusterCommunicator);
+ assertFalse("no broadcast expected", bcast.hasCaptured());
+
+ // But, Ancillary annotations will be in effect
+ DeviceDescription description3 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID, A2_2);
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+
+ DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
+ assertEquals(DEVICE_UPDATED, event3.type());
+ // basic information will be the one from Primary
+ assertDevice(DID1, SW2, event3.subject());
+ assertEquals(PID, event3.subject().providerId());
+ // but annotation from Ancillary will be merged
+ assertAnnotationsEquals(event3.subject().annotations(), A1, A2, A2_2);
+ assertTrue(deviceStore.isAvailable(DID1));
+ verify(clusterCommunicator);
+ // note: only annotation from PIDA is sent over the wire
+ assertInternalDeviceEvent(NID1, DID1, PIDA, description3,
+ asList(union(A2, A2_2)), message, subject, encoder);
+
+ }
+
+
+ @Test
+ public final void testMarkOffline() {
+
+ putDevice(DID1, SW1);
+ assertTrue(deviceStore.isAvailable(DID1));
+
+ Capture<InternalDeviceEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalDeviceEvent, byte[]>> encoder = new Capture<>();
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ DeviceEvent event = deviceStore.markOffline(DID1);
+ assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertFalse(deviceStore.isAvailable(DID1));
+ verify(clusterCommunicator);
+ // TODO: verify broadcast message
+ assertTrue(message.hasCaptured());
+
+
+ resetCommunicatorExpectingNoBroadcast(message, subject, encoder);
+ DeviceEvent event2 = deviceStore.markOffline(DID1);
+ assertNull("No change, no event", event2);
+ verify(clusterCommunicator);
+ assertFalse(message.hasCaptured());
+ }
+
+ @Test
+ public final void testUpdatePorts() {
+ putDevice(DID1, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, true)
+ );
+ Capture<InternalDeviceEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalDeviceEvent, byte[]>> encoder = new Capture<>();
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
+ verify(clusterCommunicator);
+ // TODO: verify broadcast message
+ assertTrue(message.hasCaptured());
+
+ Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+ for (DeviceEvent event : events) {
+ assertEquals(PORT_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertTrue("PortNumber is one of expected",
+ expectedPorts.remove(event.port().number()));
+ assertTrue("Port is enabled", event.port().isEnabled());
+ }
+ assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+ List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, false),
+ new DefaultPortDescription(P2, true),
+ new DefaultPortDescription(P3, true)
+ );
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ events = deviceStore.updatePorts(PID, DID1, pds2);
+ verify(clusterCommunicator);
+ // TODO: verify broadcast message
+ assertTrue(message.hasCaptured());
+
+ assertFalse("event should be triggered", events.isEmpty());
+ for (DeviceEvent event : events) {
+ PortNumber num = event.port().number();
+ if (P1.equals(num)) {
+ assertEquals(PORT_UPDATED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertFalse("Port is disabled", event.port().isEnabled());
+ } else if (P2.equals(num)) {
+ fail("P2 event not expected.");
+ } else if (P3.equals(num)) {
+ assertEquals(PORT_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertTrue("Port is enabled", event.port().isEnabled());
+ } else {
+ fail("Unknown port number encountered: " + num);
+ }
+ }
+
+ List<PortDescription> pds3 = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, false),
+ new DefaultPortDescription(P2, true)
+ );
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ events = deviceStore.updatePorts(PID, DID1, pds3);
+ verify(clusterCommunicator);
+ // TODO: verify broadcast message
+ assertTrue(message.hasCaptured());
+
+ assertFalse("event should be triggered", events.isEmpty());
+ for (DeviceEvent event : events) {
+ PortNumber num = event.port().number();
+ if (P1.equals(num)) {
+ fail("P1 event not expected.");
+ } else if (P2.equals(num)) {
+ fail("P2 event not expected.");
+ } else if (P3.equals(num)) {
+ assertEquals(PORT_REMOVED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertTrue("Port was enabled", event.port().isEnabled());
+ } else {
+ fail("Unknown port number encountered: " + num);
+ }
+ }
+ }
+
+ @Test
+ public final void testUpdatePortStatus() {
+ putDevice(DID1, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Capture<InternalPortStatusEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalPortStatusEvent, byte[]>> encoder = new Capture<>();
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ final DefaultPortDescription desc = new DefaultPortDescription(P1, false);
+ DeviceEvent event = deviceStore.updatePortStatus(PID, DID1, desc);
+ assertEquals(PORT_UPDATED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertEquals(P1, event.port().number());
+ assertFalse("Port is disabled", event.port().isEnabled());
+ verify(clusterCommunicator);
+ assertInternalPortStatusEvent(NID1, DID1, PID, desc, NO_ANNOTATION, message, subject, encoder);
+ assertTrue(message.hasCaptured());
+ }
+
+ @Test
+ public final void testUpdatePortStatusAncillary() throws IOException {
+ putDeviceAncillary(DID1, SW1);
+ putDevice(DID1, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true, A1)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Capture<InternalPortStatusEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalPortStatusEvent, byte[]>> encoder = new Capture<>();
+
+ // update port from primary
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+
+ final DefaultPortDescription desc1 = new DefaultPortDescription(P1, false, A1_2);
+ DeviceEvent event = deviceStore.updatePortStatus(PID, DID1, desc1);
+ assertEquals(PORT_UPDATED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ assertEquals(P1, event.port().number());
+ assertAnnotationsEquals(event.port().annotations(), A1, A1_2);
+ assertFalse("Port is disabled", event.port().isEnabled());
+ verify(clusterCommunicator);
+ assertInternalPortStatusEvent(NID1, DID1, PID, desc1, asList(A1, A1_2), message, subject, encoder);
+ assertTrue(message.hasCaptured());
+
+ // update port from ancillary with no attributes
+ resetCommunicatorExpectingNoBroadcast(message, subject, encoder);
+ final DefaultPortDescription desc2 = new DefaultPortDescription(P1, true);
+ DeviceEvent event2 = deviceStore.updatePortStatus(PIDA, DID1, desc2);
+ assertNull("Ancillary is ignored if primary exists", event2);
+ verify(clusterCommunicator);
+ assertFalse(message.hasCaptured());
+
+ // but, Ancillary annotation update will be notified
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ final DefaultPortDescription desc3 = new DefaultPortDescription(P1, true, A2);
+ DeviceEvent event3 = deviceStore.updatePortStatus(PIDA, DID1, desc3);
+ assertEquals(PORT_UPDATED, event3.type());
+ assertDevice(DID1, SW1, event3.subject());
+ assertEquals(P1, event3.port().number());
+ assertAnnotationsEquals(event3.port().annotations(), A1, A1_2, A2);
+ assertFalse("Port is disabled", event3.port().isEnabled());
+ verify(clusterCommunicator);
+ assertInternalPortStatusEvent(NID1, DID1, PIDA, desc3, asList(A2), message, subject, encoder);
+ assertTrue(message.hasCaptured());
+
+ // port only reported from Ancillary will be notified as down
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ final DefaultPortDescription desc4 = new DefaultPortDescription(P2, true);
+ DeviceEvent event4 = deviceStore.updatePortStatus(PIDA, DID1, desc4);
+ assertEquals(PORT_ADDED, event4.type());
+ assertDevice(DID1, SW1, event4.subject());
+ assertEquals(P2, event4.port().number());
+ assertAnnotationsEquals(event4.port().annotations());
+ assertFalse("Port is disabled if not given from primary provider",
+ event4.port().isEnabled());
+ verify(clusterCommunicator);
+ // TODO: verify broadcast message content
+ assertInternalPortStatusEvent(NID1, DID1, PIDA, desc4, NO_ANNOTATION, message, subject, encoder);
+ assertTrue(message.hasCaptured());
+ }
+
+ private void assertInternalPortStatusEvent(NodeId sender,
+ DeviceId did,
+ ProviderId pid,
+ DefaultPortDescription expectedDesc,
+ List<SparseAnnotations> expectedAnnotations,
+ Capture<InternalPortStatusEvent> actualEvent,
+ Capture<MessageSubject> actualSubject,
+ Capture<Function<InternalPortStatusEvent, byte[]>> actualEncoder) {
+
+ assertTrue(actualEvent.hasCaptured());
+ assertTrue(actualSubject.hasCaptured());
+ assertTrue(actualEncoder.hasCaptured());
+
+ assertEquals(GossipDeviceStoreMessageSubjects.PORT_STATUS_UPDATE,
+ actualSubject.getValue());
+ assertEquals(did, actualEvent.getValue().deviceId());
+ assertEquals(pid, actualEvent.getValue().providerId());
+ assertPortDescriptionEquals(expectedDesc, expectedAnnotations,
+ actualEvent.getValue().portDescription().value());
+ }
+
+ private void assertPortDescriptionEquals(
+ PortDescription expectedDesc,
+ List<SparseAnnotations> expectedAnnotations,
+ PortDescription actual) {
+
+ assertEquals(expectedDesc.portNumber(), actual.portNumber());
+ assertEquals(expectedDesc.isEnabled(), actual.isEnabled());
+
+ assertAnnotationsEquals(actual.annotations(),
+ expectedAnnotations.toArray(new SparseAnnotations[0]));
+ }
+
+ private <T> void resetCommunicatorExpectingNoBroadcast(
+ Capture<T> message,
+ Capture<MessageSubject> subject,
+ Capture<Function<T, byte[]>> encoder) {
+ message.reset();
+ subject.reset();
+ encoder.reset();
+ reset(clusterCommunicator);
+ replay(clusterCommunicator);
+ }
+
+ private <T> void resetCommunicatorExpectingSingleBroadcast(
+ Capture<T> message,
+ Capture<MessageSubject> subject,
+ Capture<Function<T, byte[]>> encoder) {
+
+ message.reset();
+ subject.reset();
+ encoder.reset();
+ reset(clusterCommunicator);
+ clusterCommunicator.broadcast(
+ capture(message),
+ capture(subject),
+ capture(encoder));
+ expectLastCall().once();
+ replay(clusterCommunicator);
+ }
+
+ @Test
+ public final void testGetPorts() {
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+ List<Port> ports = deviceStore.getPorts(DID1);
+ for (Port port : ports) {
+ assertTrue("Port is enabled", port.isEnabled());
+ assertTrue("PortNumber is one of expected",
+ expectedPorts.remove(port.number()));
+ }
+ assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+ assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
+ }
+
+ @Test
+ public final void testGetPort() {
+ putDevice(DID1, SW1);
+ putDevice(DID2, SW1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, false)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Port port1 = deviceStore.getPort(DID1, P1);
+ assertEquals(P1, port1.number());
+ assertTrue("Port is enabled", port1.isEnabled());
+
+ Port port2 = deviceStore.getPort(DID1, P2);
+ assertEquals(P2, port2.number());
+ assertFalse("Port is disabled", port2.isEnabled());
+
+ Port port3 = deviceStore.getPort(DID1, P3);
+ assertNull("P3 not expected", port3);
+ }
+
+ @Test
+ public final void testRemoveDevice() {
+ putDevice(DID1, SW1, A1);
+ List<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true, A2)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+ putDevice(DID2, SW1);
+
+ assertEquals(2, deviceStore.getDeviceCount());
+ assertEquals(1, deviceStore.getPorts(DID1).size());
+ assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations(), A1);
+ assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations(), A2);
+
+ Capture<InternalDeviceEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalDeviceEvent, byte[]>> encoder = new Capture<>();
+
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+
+ DeviceEvent event = deviceStore.removeDevice(DID1);
+ assertEquals(DEVICE_REMOVED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+
+ assertEquals(1, deviceStore.getDeviceCount());
+ assertEquals(0, deviceStore.getPorts(DID1).size());
+ verify(clusterCommunicator);
+ // TODO: verify broadcast message
+ assertTrue(message.hasCaptured());
+
+ // putBack Device, Port w/o annotation
+ putDevice(DID1, SW1);
+ List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds2);
+
+ // annotations should not survive
+ assertEquals(2, deviceStore.getDeviceCount());
+ assertEquals(1, deviceStore.getPorts(DID1).size());
+ assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations());
+ assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations());
+ }
+
+ // If Delegates should be called only on remote events,
+ // then Simple* should never call them, thus not test required.
+ // TODO add test for Port events when we have them
+ @Ignore("Ignore until Delegate spec. is clear.")
+ @Test
+ public final void testEvents() throws InterruptedException {
+ final CountDownLatch addLatch = new CountDownLatch(1);
+ DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
+ @Override
+ public void notify(DeviceEvent event) {
+ assertEquals(DEVICE_ADDED, event.type());
+ assertDevice(DID1, SW1, event.subject());
+ addLatch.countDown();
+ }
+ };
+ final CountDownLatch updateLatch = new CountDownLatch(1);
+ DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
+ @Override
+ public void notify(DeviceEvent event) {
+ assertEquals(DEVICE_UPDATED, event.type());
+ assertDevice(DID1, SW2, event.subject());
+ updateLatch.countDown();
+ }
+ };
+ final CountDownLatch removeLatch = new CountDownLatch(1);
+ DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
+ @Override
+ public void notify(DeviceEvent event) {
+ assertEquals(DEVICE_REMOVED, event.type());
+ assertDevice(DID1, SW2, event.subject());
+ removeLatch.countDown();
+ }
+ };
+
+ DeviceDescription description =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW1, SN, CID);
+ deviceStore.setDelegate(checkAdd);
+ deviceStore.createOrUpdateDevice(PID, DID1, description);
+ assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+
+
+ DeviceDescription description2 =
+ new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+ HW, SW2, SN, CID);
+ deviceStore.unsetDelegate(checkAdd);
+ deviceStore.setDelegate(checkUpdate);
+ deviceStore.createOrUpdateDevice(PID, DID1, description2);
+ assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
+
+ deviceStore.unsetDelegate(checkUpdate);
+ deviceStore.setDelegate(checkRemove);
+ deviceStore.removeDevice(DID1);
+ assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
+ }
+
+ private final class TestMastershipService extends MastershipServiceAdapter {
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ return NID1;
+ }
+ @Override
+ public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ private static final class TestGossipDeviceStore extends GossipDeviceStore {
+
+ public TestGossipDeviceStore(
+ DeviceClockService deviceClockService,
+ ClusterService clusterService,
+ ClusterCommunicationService clusterCommunicator) {
+ this.deviceClockService = deviceClockService;
+ this.clusterService = clusterService;
+ this.clusterCommunicator = clusterCommunicator;
+ }
+ }
+
+ private static final class TestClusterService extends StaticClusterService {
+
+ public TestClusterService() {
+ localNode = ONOS1;
+ nodes.put(NID1, ONOS1);
+ nodeStates.put(NID1, ACTIVE);
+
+ nodes.put(NID2, ONOS2);
+ nodeStates.put(NID2, ACTIVE);
+ }
+ }
+
+ private final class TestDeviceClockService extends DeviceClockServiceAdapter {
+
+ private final AtomicLong ticker = new AtomicLong();
+
+ @Override
+ public Timestamp getTimestamp(DeviceId deviceId) {
+ if (DID1.equals(deviceId)) {
+ return new MastershipBasedTimestamp(1, ticker.getAndIncrement());
+ } else if (DID2.equals(deviceId)) {
+ return new MastershipBasedTimestamp(2, ticker.getAndIncrement());
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public boolean isTimestampAvailable(DeviceId deviceId) {
+ return DID1.equals(deviceId) || DID2.equals(deviceId);
+ }
+ }
+
+ private class TestDatabaseManager extends DatabaseManager {
+ void init(ClusterService clusterService,
+ ClusterCommunicationService clusterCommunicator) {
+ this.clusterService = clusterService;
+ this.clusterCommunicator = clusterCommunicator;
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/PortFragmentIdTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/PortFragmentIdTest.java
new file mode 100644
index 00000000..5b862520
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/device/impl/PortFragmentIdTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.store.device.impl;
+
+import static org.onosproject.net.DeviceId.deviceId;
+
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.testing.EqualsTester;
+
+public class PortFragmentIdTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+
+ private static final PortNumber PN1 = PortNumber.portNumber(1);
+ private static final PortNumber PN2 = PortNumber.portNumber(2);
+
+ @Test
+ public final void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(new PortFragmentId(DID1, PID, PN1),
+ new PortFragmentId(DID1, PID, PN1))
+ .addEqualityGroup(new PortFragmentId(DID2, PID, PN1),
+ new PortFragmentId(DID2, PID, PN1))
+ .addEqualityGroup(new PortFragmentId(DID1, PIDA, PN1),
+ new PortFragmentId(DID1, PIDA, PN1))
+ .addEqualityGroup(new PortFragmentId(DID2, PIDA, PN1),
+ new PortFragmentId(DID2, PIDA, PN1))
+
+ .addEqualityGroup(new PortFragmentId(DID1, PID, PN2),
+ new PortFragmentId(DID1, PID, PN2))
+ .addEqualityGroup(new PortFragmentId(DID2, PID, PN2),
+ new PortFragmentId(DID2, PID, PN2))
+ .addEqualityGroup(new PortFragmentId(DID1, PIDA, PN2),
+ new PortFragmentId(DID1, PIDA, PN2))
+ .addEqualityGroup(new PortFragmentId(DID2, PIDA, PN2),
+ new PortFragmentId(DID2, PIDA, PN2))
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/EventuallyConsistentMapImplTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/EventuallyConsistentMapImplTest.java
new file mode 100644
index 00000000..ccf6ee71
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/EventuallyConsistentMapImplTest.java
@@ -0,0 +1,909 @@
+/*
+ * 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.store.ecmap;
+
+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.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationServiceAdapter;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.impl.LogicalTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.serializers.KryoSerializer;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.WallClockTimestamp;
+
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static junit.framework.TestCase.assertFalse;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit tests for EventuallyConsistentMapImpl.
+ */
+public class EventuallyConsistentMapImplTest {
+
+ private EventuallyConsistentMap<String, String> ecMap;
+
+ private ClusterService clusterService;
+ private ClusterCommunicationService clusterCommunicator;
+ private SequentialClockService<String, String> clockService;
+
+ private static final String MAP_NAME = "test";
+ private static final MessageSubject UPDATE_MESSAGE_SUBJECT
+ = new MessageSubject("ecm-" + MAP_NAME + "-update");
+ private static final MessageSubject ANTI_ENTROPY_MESSAGE_SUBJECT
+ = new MessageSubject("ecm-" + MAP_NAME + "-anti-entropy");
+
+ private static final String KEY1 = "one";
+ private static final String KEY2 = "two";
+ private static final String VALUE1 = "oneValue";
+ private static final String VALUE2 = "twoValue";
+
+ private final ControllerNode self =
+ new DefaultControllerNode(new NodeId("local"), IpAddress.valueOf(1));
+
+ private Consumer<Collection<UpdateEntry<String, String>>> updateHandler;
+ private Consumer<AntiEntropyAdvertisement<String>> antiEntropyHandler;
+
+ /*
+ * Serialization is a bit tricky here. We need to serialize in the tests
+ * to set the expectations, which will use this serializer here, but the
+ * EventuallyConsistentMap will use its own internal serializer. This means
+ * this serializer must be set up exactly the same as map's internal
+ * serializer.
+ */
+ private static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ // Classes we give to the map
+ .register(KryoNamespaces.API)
+ .register(TestTimestamp.class)
+ // Below is the classes that the map internally registers
+ .register(LogicalTimestamp.class)
+ .register(WallClockTimestamp.class)
+ .register(ArrayList.class)
+ .register(AntiEntropyAdvertisement.class)
+ .register(HashMap.class)
+ .register(Optional.class)
+ .build();
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ clusterService = createMock(ClusterService.class);
+ expect(clusterService.getLocalNode()).andReturn(self).anyTimes();
+ expect(clusterService.getNodes()).andReturn(ImmutableSet.of(self)).anyTimes();
+ replay(clusterService);
+
+ clusterCommunicator = createMock(ClusterCommunicationService.class);
+
+ // Add expectation for adding cluster message subscribers which
+ // delegate to our ClusterCommunicationService implementation. This
+ // allows us to get a reference to the map's internal cluster message
+ // handlers so we can induce events coming in from a peer.
+ clusterCommunicator.<String>addSubscriber(anyObject(MessageSubject.class),
+ anyObject(Function.class), anyObject(Consumer.class), anyObject(Executor.class));
+ expectLastCall().andDelegateTo(new TestClusterCommunicationService()).times(2);
+
+ replay(clusterCommunicator);
+
+ clockService = new SequentialClockService<>();
+
+ KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(TestTimestamp.class);
+
+ ecMap = new EventuallyConsistentMapBuilderImpl<String, String>(
+ clusterService, clusterCommunicator)
+ .withName(MAP_NAME)
+ .withSerializer(serializer)
+ .withTimestampProvider((k, v) -> clockService.getTimestamp(k, v))
+ .withCommunicationExecutor(MoreExecutors.newDirectExecutorService())
+ .build();
+
+ // Reset ready for tests to add their own expectations
+ reset(clusterCommunicator);
+ }
+
+ @After
+ public void tearDown() {
+ reset(clusterCommunicator);
+ ecMap.destroy();
+ }
+
+ @SuppressWarnings("unchecked")
+ private EventuallyConsistentMapListener<String, String> getListener() {
+ return createMock(EventuallyConsistentMapListener.class);
+ }
+
+ @Test
+ public void testSize() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertEquals(0, ecMap.size());
+ ecMap.put(KEY1, VALUE1);
+ assertEquals(1, ecMap.size());
+ ecMap.put(KEY1, VALUE2);
+ assertEquals(1, ecMap.size());
+ ecMap.put(KEY2, VALUE2);
+ assertEquals(2, ecMap.size());
+ for (int i = 0; i < 10; i++) {
+ ecMap.put("" + i, "" + i);
+ }
+ assertEquals(12, ecMap.size());
+ ecMap.remove(KEY1);
+ assertEquals(11, ecMap.size());
+ ecMap.remove(KEY1);
+ assertEquals(11, ecMap.size());
+ }
+
+ @Test
+ public void testIsEmpty() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertTrue(ecMap.isEmpty());
+ ecMap.put(KEY1, VALUE1);
+ assertFalse(ecMap.isEmpty());
+ ecMap.remove(KEY1);
+ assertTrue(ecMap.isEmpty());
+ }
+
+ @Test
+ public void testContainsKey() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertFalse(ecMap.containsKey(KEY1));
+ ecMap.put(KEY1, VALUE1);
+ assertTrue(ecMap.containsKey(KEY1));
+ assertFalse(ecMap.containsKey(KEY2));
+ ecMap.remove(KEY1);
+ assertFalse(ecMap.containsKey(KEY1));
+ }
+
+ @Test
+ public void testContainsValue() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertFalse(ecMap.containsValue(VALUE1));
+ ecMap.put(KEY1, VALUE1);
+ assertTrue(ecMap.containsValue(VALUE1));
+ assertFalse(ecMap.containsValue(VALUE2));
+ ecMap.put(KEY1, VALUE2);
+ assertFalse(ecMap.containsValue(VALUE1));
+ assertTrue(ecMap.containsValue(VALUE2));
+ ecMap.remove(KEY1);
+ assertFalse(ecMap.containsValue(VALUE2));
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ CountDownLatch latch;
+
+ // Local put
+ assertNull(ecMap.get(KEY1));
+ ecMap.put(KEY1, VALUE1);
+ assertEquals(VALUE1, ecMap.get(KEY1));
+
+ // Remote put
+ List<UpdateEntry<String, String>> message
+ = ImmutableList.of(generatePutMessage(KEY2, VALUE2, clockService.getTimestamp(KEY2, VALUE2)));
+
+ // Create a latch so we know when the put operation has finished
+ latch = new CountDownLatch(1);
+ ecMap.addListener(new TestListener(latch));
+
+ assertNull(ecMap.get(KEY2));
+ updateHandler.accept(message);
+ assertTrue("External listener never got notified of internal event",
+ latch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(VALUE2, ecMap.get(KEY2));
+
+ // Local remove
+ ecMap.remove(KEY2);
+ assertNull(ecMap.get(KEY2));
+
+ // Remote remove
+ message = ImmutableList.of(generateRemoveMessage(KEY1, clockService.getTimestamp(KEY1, VALUE1)));
+
+ // Create a latch so we know when the remove operation has finished
+ latch = new CountDownLatch(1);
+ ecMap.addListener(new TestListener(latch));
+
+ updateHandler.accept(message);
+ assertTrue("External listener never got notified of internal event",
+ latch.await(100, TimeUnit.MILLISECONDS));
+ assertNull(ecMap.get(KEY1));
+ }
+
+ @Test
+ public void testPut() throws Exception {
+ // Set up expectations of external events to be sent to listeners during
+ // the test. These don't use timestamps so we can set them all up at once.
+ EventuallyConsistentMapListener<String, String> listener
+ = getListener();
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE2));
+ replay(listener);
+
+ ecMap.addListener(listener);
+
+ // Set up expected internal message to be broadcast to peers on first put
+ expectSpecificMulticastMessage(generatePutMessage(KEY1, VALUE1, clockService
+ .peekAtNextTimestamp()), UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ // Put first value
+ assertNull(ecMap.get(KEY1));
+ ecMap.put(KEY1, VALUE1);
+ assertEquals(VALUE1, ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+ // Set up expected internal message to be broadcast to peers on second put
+ expectSpecificMulticastMessage(generatePutMessage(
+ KEY1, VALUE2, clockService.peekAtNextTimestamp()), UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ // Update same key to a new value
+ ecMap.put(KEY1, VALUE2);
+ assertEquals(VALUE2, ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+ // Do a put with a older timestamp than the value already there.
+ // The map data should not be changed and no notifications should be sent.
+ reset(clusterCommunicator);
+ replay(clusterCommunicator);
+
+ clockService.turnBackTime();
+ ecMap.put(KEY1, VALUE1);
+ // Value should not have changed.
+ assertEquals(VALUE2, ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+ // Check that our listener received the correct events during the test
+ verify(listener);
+ }
+
+ @Test
+ public void testRemove() throws Exception {
+ // Set up expectations of external events to be sent to listeners during
+ // the test. These don't use timestamps so we can set them all up at once.
+ EventuallyConsistentMapListener<String, String> listener
+ = getListener();
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY2, VALUE2));
+ replay(listener);
+
+ ecMap.addListener(listener);
+
+ // Put in an initial value
+ expectPeerMessage(clusterCommunicator);
+ ecMap.put(KEY1, VALUE1);
+ assertEquals(VALUE1, ecMap.get(KEY1));
+
+ // Remove the value and check the correct internal cluster messages
+ // are sent
+ expectSpecificMulticastMessage(generateRemoveMessage(KEY1, clockService.peekAtNextTimestamp()),
+ UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ ecMap.remove(KEY1);
+ assertNull(ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+ // Remove the same value again. Even though the value is no longer in
+ // the map, we expect that the tombstone is updated and another remove
+ // event is sent to the cluster and external listeners.
+ expectSpecificMulticastMessage(generateRemoveMessage(KEY1, clockService.peekAtNextTimestamp()),
+ UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ ecMap.remove(KEY1);
+ assertNull(ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+
+ // Put in a new value for us to try and remove
+ expectPeerMessage(clusterCommunicator);
+
+ ecMap.put(KEY2, VALUE2);
+
+ clockService.turnBackTime();
+
+ // Remove should have no effect, since it has an older timestamp than
+ // the put. Expect no notifications to be sent out
+ reset(clusterCommunicator);
+ replay(clusterCommunicator);
+
+ ecMap.remove(KEY2);
+
+ verify(clusterCommunicator);
+
+ // Check that our listener received the correct events during the test
+ verify(listener);
+ }
+
+ @Test
+ public void testCompute() throws Exception {
+ // Set up expectations of external events to be sent to listeners during
+ // the test. These don't use timestamps so we can set them all up at once.
+ EventuallyConsistentMapListener<String, String> listener
+ = getListener();
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY2, VALUE2));
+ replay(listener);
+
+ ecMap.addListener(listener);
+
+ // Put in an initial value
+ expectPeerMessage(clusterCommunicator);
+ ecMap.compute(KEY1, (k, v) -> VALUE1);
+ assertEquals(VALUE1, ecMap.get(KEY1));
+
+ // Remove the value and check the correct internal cluster messages
+ // are sent
+ expectSpecificMulticastMessage(generateRemoveMessage(KEY1, clockService.peekAtNextTimestamp()),
+ UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ ecMap.compute(KEY1, (k, v) -> null);
+ assertNull(ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+ // Remove the same value again. Even though the value is no longer in
+ // the map, we expect that the tombstone is updated and another remove
+ // event is sent to the cluster and external listeners.
+ expectSpecificMulticastMessage(generateRemoveMessage(KEY1, clockService.peekAtNextTimestamp()),
+ UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ ecMap.compute(KEY1, (k, v) -> null);
+ assertNull(ecMap.get(KEY1));
+
+ verify(clusterCommunicator);
+
+ // Put in a new value for us to try and remove
+ expectPeerMessage(clusterCommunicator);
+
+ ecMap.compute(KEY2, (k, v) -> VALUE2);
+
+ clockService.turnBackTime();
+
+ // Remove should have no effect, since it has an older timestamp than
+ // the put. Expect no notifications to be sent out
+ reset(clusterCommunicator);
+ replay(clusterCommunicator);
+
+ ecMap.compute(KEY2, (k, v) -> null);
+
+ verify(clusterCommunicator);
+
+ // Check that our listener received the correct events during the test
+ verify(listener);
+ }
+
+ @Test
+ public void testPutAll() throws Exception {
+ // putAll() with an empty map is a no-op - no messages will be sent
+ reset(clusterCommunicator);
+ replay(clusterCommunicator);
+
+ ecMap.putAll(new HashMap<>());
+
+ verify(clusterCommunicator);
+
+ // Set up the listener with our expected events
+ EventuallyConsistentMapListener<String, String> listener
+ = getListener();
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.PUT, KEY2, VALUE2));
+ replay(listener);
+
+ ecMap.addListener(listener);
+
+ // Expect a multi-update inter-instance message
+ expectSpecificBroadcastMessage(generatePutMessage(KEY1, VALUE1, KEY2, VALUE2), UPDATE_MESSAGE_SUBJECT,
+ clusterCommunicator);
+
+ Map<String, String> putAllValues = new HashMap<>();
+ putAllValues.put(KEY1, VALUE1);
+ putAllValues.put(KEY2, VALUE2);
+
+ // Put the values in the map
+ ecMap.putAll(putAllValues);
+
+ // Check the correct messages and events were sent
+ verify(clusterCommunicator);
+ verify(listener);
+ }
+
+ @Test
+ public void testClear() throws Exception {
+ EventuallyConsistentMapListener<String, String> listener
+ = getListener();
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1));
+ listener.event(new EventuallyConsistentMapEvent<>(
+ MAP_NAME, EventuallyConsistentMapEvent.Type.REMOVE, KEY2, VALUE2));
+ replay(listener);
+
+ // clear() on an empty map is a no-op - no messages will be sent
+ reset(clusterCommunicator);
+ replay(clusterCommunicator);
+
+ assertTrue(ecMap.isEmpty());
+ ecMap.clear();
+ verify(clusterCommunicator);
+
+ // Put some items in the map
+ expectPeerMessage(clusterCommunicator);
+ ecMap.put(KEY1, VALUE1);
+ ecMap.put(KEY2, VALUE2);
+
+ ecMap.addListener(listener);
+ expectSpecificBroadcastMessage(generateRemoveMessage(KEY1, KEY2), UPDATE_MESSAGE_SUBJECT, clusterCommunicator);
+
+ ecMap.clear();
+
+ verify(clusterCommunicator);
+ verify(listener);
+ }
+
+ @Test
+ public void testKeySet() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertTrue(ecMap.keySet().isEmpty());
+
+ // Generate some keys
+ Set<String> keys = new HashSet<>();
+ for (int i = 1; i <= 10; i++) {
+ keys.add("" + i);
+ }
+
+ // Put each key in the map
+ keys.forEach(k -> ecMap.put(k, "value" + k));
+
+ // Check keySet() returns the correct value
+ assertEquals(keys, ecMap.keySet());
+
+ // Update the value for one of the keys
+ ecMap.put(keys.iterator().next(), "new-value");
+
+ // Check the key set is still the same
+ assertEquals(keys, ecMap.keySet());
+
+ // Remove a key
+ String removeKey = keys.iterator().next();
+ keys.remove(removeKey);
+ ecMap.remove(removeKey);
+
+ // Check the key set is still correct
+ assertEquals(keys, ecMap.keySet());
+ }
+
+ @Test
+ public void testValues() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertTrue(ecMap.values().isEmpty());
+
+ // Generate some values
+ Map<String, String> expectedValues = new HashMap<>();
+ for (int i = 1; i <= 10; i++) {
+ expectedValues.put("" + i, "value" + i);
+ }
+
+ // Add them into the map
+ expectedValues.entrySet().forEach(e -> ecMap.put(e.getKey(), e.getValue()));
+
+ // Check the values collection is correct
+ assertEquals(expectedValues.values().size(), ecMap.values().size());
+ expectedValues.values().forEach(v -> assertTrue(ecMap.values().contains(v)));
+
+ // Update the value for one of the keys
+ Map.Entry<String, String> first = expectedValues.entrySet().iterator().next();
+ expectedValues.put(first.getKey(), "new-value");
+ ecMap.put(first.getKey(), "new-value");
+
+ // Check the values collection is still correct
+ assertEquals(expectedValues.values().size(), ecMap.values().size());
+ expectedValues.values().forEach(v -> assertTrue(ecMap.values().contains(v)));
+
+ // Remove a key
+ String removeKey = expectedValues.keySet().iterator().next();
+ expectedValues.remove(removeKey);
+ ecMap.remove(removeKey);
+
+ // Check the values collection is still correct
+ assertEquals(expectedValues.values().size(), ecMap.values().size());
+ expectedValues.values().forEach(v -> assertTrue(ecMap.values().contains(v)));
+ }
+
+ @Test
+ public void testEntrySet() throws Exception {
+ expectPeerMessage(clusterCommunicator);
+
+ assertTrue(ecMap.entrySet().isEmpty());
+
+ // Generate some values
+ Map<String, String> expectedValues = new HashMap<>();
+ for (int i = 1; i <= 10; i++) {
+ expectedValues.put("" + i, "value" + i);
+ }
+
+ // Add them into the map
+ expectedValues.entrySet().forEach(e -> ecMap.put(e.getKey(), e.getValue()));
+
+ // Check the entry set is correct
+ assertTrue(entrySetsAreEqual(expectedValues, ecMap.entrySet()));
+
+ // Update the value for one of the keys
+ Map.Entry<String, String> first = expectedValues.entrySet().iterator().next();
+ expectedValues.put(first.getKey(), "new-value");
+ ecMap.put(first.getKey(), "new-value");
+
+ // Check the entry set is still correct
+ assertTrue(entrySetsAreEqual(expectedValues, ecMap.entrySet()));
+
+ // Remove a key
+ String removeKey = expectedValues.keySet().iterator().next();
+ expectedValues.remove(removeKey);
+ ecMap.remove(removeKey);
+
+ // Check the entry set is still correct
+ assertTrue(entrySetsAreEqual(expectedValues, ecMap.entrySet()));
+ }
+
+ private static boolean entrySetsAreEqual(Map<String, String> expectedMap, Set<Map.Entry<String, String>> actual) {
+ if (expectedMap.entrySet().size() != actual.size()) {
+ return false;
+ }
+
+ for (Map.Entry<String, String> e : actual) {
+ if (!expectedMap.containsKey(e.getKey())) {
+ return false;
+ }
+ if (!Objects.equals(expectedMap.get(e.getKey()), e.getValue())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Test
+ public void testDestroy() throws Exception {
+ clusterCommunicator.removeSubscriber(UPDATE_MESSAGE_SUBJECT);
+ clusterCommunicator.removeSubscriber(ANTI_ENTROPY_MESSAGE_SUBJECT);
+
+ replay(clusterCommunicator);
+
+ ecMap.destroy();
+
+ verify(clusterCommunicator);
+
+ try {
+ ecMap.get(KEY1);
+ fail("get after destroy should throw exception");
+ } catch (IllegalStateException e) {
+ assertTrue(true);
+ }
+
+ try {
+ ecMap.put(KEY1, VALUE1);
+ fail("put after destroy should throw exception");
+ } catch (IllegalStateException e) {
+ assertTrue(true);
+ }
+ }
+
+ private UpdateEntry<String, String> generatePutMessage(String key, String value, Timestamp timestamp) {
+ return new UpdateEntry<>(key, new MapValue<>(value, timestamp));
+ }
+
+ private List<UpdateEntry<String, String>> generatePutMessage(
+ String key1, String value1, String key2, String value2) {
+ List<UpdateEntry<String, String>> list = new ArrayList<>();
+
+ Timestamp timestamp1 = clockService.peek(1);
+ Timestamp timestamp2 = clockService.peek(2);
+
+ list.add(generatePutMessage(key1, value1, timestamp1));
+ list.add(generatePutMessage(key2, value2, timestamp2));
+
+ return list;
+ }
+
+ private UpdateEntry<String, String> generateRemoveMessage(String key, Timestamp timestamp) {
+ return new UpdateEntry<>(key, new MapValue<>(null, timestamp));
+ }
+
+ private List<UpdateEntry<String, String>> generateRemoveMessage(String key1, String key2) {
+ List<UpdateEntry<String, String>> list = new ArrayList<>();
+
+ Timestamp timestamp1 = clockService.peek(1);
+ Timestamp timestamp2 = clockService.peek(2);
+
+ list.add(generateRemoveMessage(key1, timestamp1));
+ list.add(generateRemoveMessage(key2, timestamp2));
+
+ return list;
+ }
+
+ /**
+ * Sets up a mock ClusterCommunicationService to expect a specific cluster
+ * message to be broadcast to the cluster.
+ *
+ * @param message message we expect to be sent
+ * @param clusterCommunicator a mock ClusterCommunicationService to set up
+ */
+ //FIXME rename
+ private static <T> void expectSpecificBroadcastMessage(
+ T message,
+ MessageSubject subject,
+ ClusterCommunicationService clusterCommunicator) {
+ reset(clusterCommunicator);
+ clusterCommunicator.<T>multicast(eq(message), eq(subject), anyObject(Function.class), anyObject(Set.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+ }
+
+ /**
+ * Sets up a mock ClusterCommunicationService to expect a specific cluster
+ * message to be multicast to the cluster.
+ *
+ * @param message message we expect to be sent
+ * @param subject subject we expect to be sent to
+ * @param clusterCommunicator a mock ClusterCommunicationService to set up
+ */
+ //FIXME rename
+ private static <T> void expectSpecificMulticastMessage(T message, MessageSubject subject,
+ ClusterCommunicationService clusterCommunicator) {
+ reset(clusterCommunicator);
+ clusterCommunicator.<T>multicast(eq(message), eq(subject), anyObject(Function.class), anyObject(Set.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+ }
+
+
+ /**
+ * Sets up a mock ClusterCommunicationService to expect a multicast cluster message
+ * that is sent to it. This is useful for unit tests where we aren't
+ * interested in testing the messaging component.
+ *
+ * @param clusterCommunicator a mock ClusterCommunicationService to set up
+ */
+ //FIXME rename
+ private <T> void expectPeerMessage(ClusterCommunicationService clusterCommunicator) {
+ reset(clusterCommunicator);
+// expect(clusterCommunicator.multicast(anyObject(ClusterMessage.class),
+// anyObject(Iterable.class)))
+ expect(clusterCommunicator.<T>unicast(
+ anyObject(),
+ anyObject(MessageSubject.class),
+ anyObject(Function.class),
+ anyObject(NodeId.class)))
+ .andReturn(CompletableFuture.completedFuture(null))
+ .anyTimes();
+ replay(clusterCommunicator);
+ }
+
+ /**
+ * Sets up a mock ClusterCommunicationService to expect a broadcast cluster message
+ * that is sent to it. This is useful for unit tests where we aren't
+ * interested in testing the messaging component.
+ *
+ * @param clusterCommunicator a mock ClusterCommunicationService to set up
+ */
+ private void expectBroadcastMessage(ClusterCommunicationService clusterCommunicator) {
+ reset(clusterCommunicator);
+ clusterCommunicator.<AbstractEvent>multicast(
+ anyObject(AbstractEvent.class),
+ anyObject(MessageSubject.class),
+ anyObject(Function.class),
+ anyObject(Set.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+ }
+
+ /**
+ * ClusterCommunicationService implementation that the map's addSubscriber
+ * call will delegate to. This means we can get a reference to the
+ * internal cluster message handler used by the map, so that we can simulate
+ * events coming in from other instances.
+ */
+ private final class TestClusterCommunicationService
+ extends ClusterCommunicationServiceAdapter {
+
+ @Override
+ public <M> void addSubscriber(MessageSubject subject,
+ Function<byte[], M> decoder, Consumer<M> handler,
+ Executor executor) {
+ if (subject.equals(UPDATE_MESSAGE_SUBJECT)) {
+ updateHandler = (Consumer<Collection<UpdateEntry<String, String>>>) handler;
+ } else if (subject.equals(ANTI_ENTROPY_MESSAGE_SUBJECT)) {
+ antiEntropyHandler = (Consumer<AntiEntropyAdvertisement<String>>) handler;
+ } else {
+ throw new RuntimeException("Unexpected message subject " + subject.toString());
+ }
+ }
+ }
+
+ /**
+ * ClockService implementation that gives out timestamps based on a
+ * sequential counter. This clock service enables more control over the
+ * timestamps that are given out, including being able to "turn back time"
+ * to give out timestamps from the past.
+ *
+ * @param <T> Type that the clock service will give out timestamps for
+ * @param <U> Second type that the clock service will give out values for
+ */
+ private class SequentialClockService<T, U> {
+
+ private static final long INITIAL_VALUE = 1;
+ private final AtomicLong counter = new AtomicLong(INITIAL_VALUE);
+
+ public Timestamp getTimestamp(T object, U object2) {
+ return new TestTimestamp(counter.getAndIncrement());
+ }
+
+ /**
+ * Returns what the next timestamp will be without consuming the
+ * timestamp. This allows test code to set expectations correctly while
+ * still allowing the CUT to get the same timestamp.
+ *
+ * @return timestamp equal to the timestamp that will be returned by the
+ * next call to {@link #getTimestamp(T, U)}.
+ */
+ public Timestamp peekAtNextTimestamp() {
+ return peek(1);
+ }
+
+ /**
+ * Returns the ith timestamp to be given out in the future without
+ * consuming the timestamp. For example, i=1 returns the next timestamp,
+ * i=2 returns the timestamp after that, and so on.
+ *
+ * @param i number of the timestamp to peek at
+ * @return the ith timestamp that will be given out
+ */
+ public Timestamp peek(int i) {
+ checkArgument(i > 0, "i must be a positive integer");
+
+ return new TestTimestamp(counter.get() + i - 1);
+ }
+
+ /**
+ * Turns the clock back two ticks, so the next call to getTimestamp will
+ * return an older timestamp than the previous call to getTimestamp.
+ */
+ public void turnBackTime() {
+ // Not atomic, but should be OK for these tests.
+ counter.decrementAndGet();
+ counter.decrementAndGet();
+ }
+
+ }
+
+ /**
+ * Timestamp implementation where the value of the timestamp can be
+ * specified explicitly at creation time.
+ */
+ private class TestTimestamp implements Timestamp {
+
+ private final long timestamp;
+
+ /**
+ * Creates a new timestamp that has the specified value.
+ *
+ * @param timestamp value of the timestamp
+ */
+ public TestTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof TestTimestamp);
+ TestTimestamp otherTimestamp = (TestTimestamp) o;
+ return ComparisonChain.start()
+ .compare(this.timestamp, otherTimestamp.timestamp)
+ .result();
+ }
+ }
+
+ /**
+ * EventuallyConsistentMapListener implementation which triggers a latch
+ * when it receives an event.
+ */
+ private class TestListener implements EventuallyConsistentMapListener<String, String> {
+ private CountDownLatch latch;
+
+ /**
+ * Creates a new listener that will trigger the specified latch when it
+ * receives and event.
+ *
+ * @param latch the latch to trigger on events
+ */
+ public TestListener(CountDownLatch latch) {
+ this.latch = latch;
+ }
+
+ @Override
+ public void event(EventuallyConsistentMapEvent<String, String> event) {
+ latch.countDown();
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/MapValueTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/MapValueTest.java
new file mode 100644
index 00000000..d6dca9b8
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/ecmap/MapValueTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.store.ecmap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.ecmap.MapValue.Digest;
+import org.onosproject.store.impl.LogicalTimestamp;
+
+/**
+ * Unit tests for MapValue.
+ */
+public class MapValueTest {
+
+ @Test
+ public void testConstruction() {
+ Timestamp ts = new LogicalTimestamp(10);
+ MapValue<String> mv = new MapValue<>("foo", ts);
+ assertEquals("foo", mv.get());
+ assertEquals(ts, mv.timestamp());
+ assertTrue(mv.isAlive());
+ }
+
+ @Test
+ public void testDigest() {
+ Timestamp ts = new LogicalTimestamp(10);
+ MapValue<String> mv = new MapValue<>("foo", ts);
+ Digest actual = mv.digest();
+ Digest expected = new MapValue.Digest(ts, false);
+ assertEquals(actual, expected);
+ }
+
+ @Test
+ public void testComparison() {
+ Timestamp ts1 = new LogicalTimestamp(9);
+ Timestamp ts2 = new LogicalTimestamp(10);
+ Timestamp ts3 = new LogicalTimestamp(11);
+ MapValue<String> mv1 = new MapValue<>("foo", ts1);
+ MapValue<String> mv2 = new MapValue<>("foo", ts2);
+ MapValue<String> mv3 = new MapValue<>("foo", ts3);
+ assertTrue(mv2.isNewerThan(mv1));
+ assertFalse(mv1.isNewerThan(mv3));
+
+ assertTrue(mv3.isNewerThan(ts2));
+ assertFalse(mv1.isNewerThan(ts2));
+
+ assertTrue(mv1.compareTo(mv2) < 0);
+ assertTrue(mv1.compareTo(mv1) == 0);
+ assertTrue(mv3.compareTo(mv2) > 0);
+ }
+
+ @Test
+ public void testTombstone() {
+ Timestamp ts1 = new LogicalTimestamp(9);
+ MapValue<String> mv = MapValue.tombstone(ts1);
+ assertTrue(mv.isTombstone());
+ assertFalse(mv.isAlive());
+ assertNull(mv.get());
+ assertEquals(ts1, mv.timestamp());
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ReplicaInfoManagerTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ReplicaInfoManagerTest.java
new file mode 100644
index 00000000..d429752c
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ReplicaInfoManagerTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.store.flow.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipEvent.Type;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.flow.ReplicaInfo;
+import org.onosproject.store.flow.ReplicaInfoEvent;
+import org.onosproject.store.flow.ReplicaInfoEventListener;
+import org.onosproject.store.flow.ReplicaInfoService;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ReplicaInfoManagerTest {
+
+
+ private static final DeviceId DID1 = DeviceId.deviceId("of:1");
+ private static final DeviceId DID2 = DeviceId.deviceId("of:2");
+ private static final NodeId NID1 = new NodeId("foo");
+
+ private ReplicaInfoManager mgr;
+ private ReplicaInfoService service;
+
+ private ListenerRegistry<MastershipEvent, MastershipListener>
+ mastershipListenerRegistry;
+ private TestEventDispatcher eventDispatcher;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mastershipListenerRegistry = new ListenerRegistry<>();
+
+ mgr = new ReplicaInfoManager();
+ service = mgr;
+
+ eventDispatcher = new TestEventDispatcher();
+ mgr.eventDispatcher = eventDispatcher;
+ mgr.mastershipService = new TestMastershipService();
+
+ // register dummy mastership event source
+ mgr.eventDispatcher.addSink(MastershipEvent.class, mastershipListenerRegistry);
+
+ mgr.activate();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mgr.deactivate();
+ }
+
+ @Test
+ public void testGetReplicaInfoFor() {
+ ReplicaInfo info1 = service.getReplicaInfoFor(DID1);
+ assertEquals(Optional.of(NID1), info1.master());
+ // backups are always empty for now
+ assertEquals(Collections.emptyList(), info1.backups());
+
+ ReplicaInfo info2 = service.getReplicaInfoFor(DID2);
+ assertEquals("There's no master", Optional.absent(), info2.master());
+ // backups are always empty for now
+ assertEquals(Collections.emptyList(), info2.backups());
+ }
+
+ @Test
+ public void testReplicaInfoEvent() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ service.addListener(new MasterNodeCheck(latch, DID1, NID1));
+
+ // fake MastershipEvent
+ eventDispatcher.post(new MastershipEvent(Type.MASTER_CHANGED, DID1,
+ new RoleInfo(NID1, new LinkedList<NodeId>())));
+
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ }
+
+
+ private final class MasterNodeCheck implements ReplicaInfoEventListener {
+ private final CountDownLatch latch;
+ private Optional<NodeId> expectedMaster;
+ private DeviceId expectedDevice;
+
+
+ MasterNodeCheck(CountDownLatch latch, DeviceId did,
+ NodeId nid) {
+ this.latch = latch;
+ this.expectedMaster = Optional.fromNullable(nid);
+ this.expectedDevice = did;
+ }
+
+ @Override
+ public void event(ReplicaInfoEvent event) {
+ assertEquals(expectedDevice, event.subject());
+ assertEquals(expectedMaster, event.replicaInfo().master());
+ // backups are always empty for now
+ assertEquals(Collections.emptyList(), event.replicaInfo().backups());
+ latch.countDown();
+ }
+ }
+
+
+ private final class TestMastershipService
+ extends MastershipServiceAdapter
+ implements MastershipService {
+
+ private Map<DeviceId, NodeId> masters;
+
+ TestMastershipService() {
+ masters = Maps.newHashMap();
+ masters.put(DID1, NID1);
+ // DID2 has no master
+ }
+
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ return masters.get(deviceId);
+ }
+
+ @Override
+ public RoleInfo getNodesFor(DeviceId deviceId) {
+ return new RoleInfo(masters.get(deviceId), Collections.emptyList());
+ }
+
+ @Override
+ public void addListener(MastershipListener listener) {
+ mastershipListenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(MastershipListener listener) {
+ mastershipListenerRegistry.removeListener(listener);
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStoreTest.java
new file mode 100644
index 00000000..efa226c1
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/flowobjective/impl/DistributedFlowObjectiveStoreTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.store.flowobjective.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.behaviour.DefaultNextGroup;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+import org.onosproject.store.service.TestStorageService;
+
+import com.google.common.base.Charsets;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * Unit tests for distributed flow objective store.
+ */
+public class DistributedFlowObjectiveStoreTest {
+ DistributedFlowObjectiveStore storeImpl;
+ FlowObjectiveStore store;
+
+ @Before
+ public void setUp() {
+ storeImpl = new DistributedFlowObjectiveStore();
+ storeImpl.storageService = new TestStorageService();
+ storeImpl.activate();
+ store = storeImpl;
+ }
+
+ @After
+ public void tearDown() {
+ storeImpl.deactivate();
+ }
+
+ @Test
+ public void testFlowObjectiveStore() {
+ NextGroup group2 = new DefaultNextGroup("2".getBytes(Charsets.US_ASCII));
+ int group1Id = store.allocateNextId();
+ int group2Id = store.allocateNextId();
+
+ NextGroup group1add = store.getNextGroup(group1Id);
+ assertThat(group1add, nullValue());
+
+ store.putNextGroup(group2Id, group2);
+ NextGroup group2Query = store.getNextGroup(group2Id);
+ assertThat(group2Query.data(), is(group2.data()));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java
new file mode 100644
index 00000000..560fdb3a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java
@@ -0,0 +1,420 @@
+/*
+ * 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.store.group.impl;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.PortNumber;
+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.GroupOperation;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationServiceAdapter;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.TestStorageService;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+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.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.did;
+
+/**
+ * Distributed group store test.
+ */
+public class DistributedGroupStoreTest {
+
+ DeviceId deviceId1 = did("dev1");
+ DeviceId deviceId2 = did("dev2");
+ GroupId groupId1 = new DefaultGroupId(1);
+ GroupId groupId2 = new DefaultGroupId(2);
+ GroupKey groupKey1 = new DefaultGroupKey("abc".getBytes());
+ GroupKey groupKey2 = new DefaultGroupKey("def".getBytes());
+
+ TrafficTreatment treatment =
+ DefaultTrafficTreatment.emptyTreatment();
+ GroupBucket selectGroupBucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment);
+ GroupBucket failoverGroupBucket =
+ DefaultGroupBucket.createFailoverGroupBucket(treatment,
+ PortNumber.IN_PORT, groupId1);
+
+ GroupBuckets buckets = new GroupBuckets(ImmutableList.of(selectGroupBucket));
+ GroupDescription groupDescription1 = new DefaultGroupDescription(
+ deviceId1,
+ GroupDescription.Type.INDIRECT,
+ buckets,
+ groupKey1,
+ groupId1.id(),
+ APP_ID);
+ GroupDescription groupDescription2 = new DefaultGroupDescription(
+ deviceId2,
+ GroupDescription.Type.INDIRECT,
+ buckets,
+ groupKey2,
+ groupId2.id(),
+ APP_ID);
+
+ DistributedGroupStore groupStoreImpl;
+ GroupStore groupStore;
+ EventuallyConsistentMap auditPendingReqQueue;
+
+ static class MasterOfAll extends MastershipServiceAdapter {
+ @Override
+ public MastershipRole getLocalRole(DeviceId deviceId) {
+ return MastershipRole.MASTER;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ groupStoreImpl = new DistributedGroupStore();
+ groupStoreImpl.storageService = new TestStorageService();
+ groupStoreImpl.clusterCommunicator = new ClusterCommunicationServiceAdapter();
+ groupStoreImpl.mastershipService = new MasterOfAll();
+ groupStoreImpl.activate();
+ groupStore = groupStoreImpl;
+ auditPendingReqQueue =
+ TestUtils.getField(groupStoreImpl, "auditPendingReqQueue");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ groupStoreImpl.deactivate();
+ }
+
+ /**
+ * Tests the initial state of the store.
+ */
+ @Test
+ public void testEmptyStore() {
+ assertThat(groupStore.getGroupCount(deviceId1), is(0));
+ assertThat(groupStore.getGroup(deviceId1, groupId1), nullValue());
+ assertThat(groupStore.getGroup(deviceId1, groupKey1), nullValue());
+ }
+
+ /**
+ * Tests adding a pending group.
+ */
+ @Test
+ public void testAddPendingGroup() throws Exception {
+ // Make sure the pending list starts out empty
+ assertThat(auditPendingReqQueue.size(), is(0));
+
+ // Add a new pending group. Make sure that the store remains empty
+ groupStore.storeGroupDescription(groupDescription1);
+ assertThat(groupStore.getGroupCount(deviceId1), is(0));
+ assertThat(groupStore.getGroup(deviceId1, groupId1), nullValue());
+ assertThat(groupStore.getGroup(deviceId1, groupKey1), nullValue());
+
+ // Make sure the group is pending
+ assertThat(auditPendingReqQueue.size(), is(1));
+
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+
+ // Make sure the group isn't pending anymore
+ assertThat(auditPendingReqQueue.size(), is(0));
+ }
+
+
+ /**
+ * Tests adding and removing a group.
+ */
+ @Test
+ public void testAddRemoveGroup() throws Exception {
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+ assertThat(groupStore.deviceInitialAuditStatus(deviceId1), is(true));
+
+ // Make sure the pending list starts out empty
+ assertThat(auditPendingReqQueue.size(), is(0));
+
+ groupStore.storeGroupDescription(groupDescription1);
+ assertThat(groupStore.getGroupCount(deviceId1), is(1));
+ assertThat(groupStore.getGroup(deviceId1, groupId1), notNullValue());
+ assertThat(groupStore.getGroup(deviceId1, groupKey1), notNullValue());
+
+ // Make sure that nothing is pending
+ assertThat(auditPendingReqQueue.size(), is(0));
+
+ Group groupById = groupStore.getGroup(deviceId1, groupId1);
+ Group groupByKey = groupStore.getGroup(deviceId1, groupKey1);
+ assertThat(groupById, notNullValue());
+ assertThat(groupByKey, notNullValue());
+ assertThat(groupById, is(groupByKey));
+ assertThat(groupById.deviceId(), is(did("dev1")));
+
+ groupStore.removeGroupEntry(groupById);
+
+ assertThat(groupStore.getGroupCount(deviceId1), is(0));
+ assertThat(groupStore.getGroup(deviceId1, groupId1), nullValue());
+ assertThat(groupStore.getGroup(deviceId1, groupKey1), nullValue());
+
+ // Make sure that nothing is pending
+ assertThat(auditPendingReqQueue.size(), is(0));
+ }
+
+ /**
+ * Tests adding and removing a group.
+ */
+ @Test
+ public void testRemoveGroupDescription() throws Exception {
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+
+ groupStore.storeGroupDescription(groupDescription1);
+
+ groupStore.deleteGroupDescription(deviceId1, groupKey1);
+
+ // Group should still be there, marked for removal
+ assertThat(groupStore.getGroupCount(deviceId1), is(1));
+ Group queriedGroup = groupStore.getGroup(deviceId1, groupId1);
+ assertThat(queriedGroup.state(), is(Group.GroupState.PENDING_DELETE));
+
+ }
+
+ /**
+ * Tests pushing group metrics.
+ */
+ @Test
+ public void testPushGroupMetrics() {
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+ groupStore.deviceInitialAuditCompleted(deviceId2, true);
+
+ GroupDescription groupDescription3 = new DefaultGroupDescription(
+ deviceId1,
+ GroupDescription.Type.SELECT,
+ buckets,
+ new DefaultGroupKey("aaa".getBytes()),
+ null,
+ APP_ID);
+
+ groupStore.storeGroupDescription(groupDescription1);
+ groupStore.storeGroupDescription(groupDescription2);
+ groupStore.storeGroupDescription(groupDescription3);
+ Group group1 = groupStore.getGroup(deviceId1, groupId1);
+
+ assertThat(group1, instanceOf(DefaultGroup.class));
+ DefaultGroup defaultGroup1 = (DefaultGroup) group1;
+ defaultGroup1.setPackets(55L);
+ defaultGroup1.setBytes(66L);
+ groupStore.pushGroupMetrics(deviceId1, ImmutableList.of(group1));
+
+ // Make sure the group was updated.
+
+ Group requeryGroup1 = groupStore.getGroup(deviceId1, groupId1);
+ assertThat(requeryGroup1.packets(), is(55L));
+ assertThat(requeryGroup1.bytes(), is(66L));
+
+ }
+
+ class TestDelegate implements GroupStoreDelegate {
+ private List<GroupEvent> eventsSeen = new LinkedList<>();
+ @Override
+ public void notify(GroupEvent event) {
+ eventsSeen.add(event);
+ }
+
+ public List<GroupEvent> eventsSeen() {
+ return eventsSeen;
+ }
+
+ public void resetEvents() {
+ eventsSeen.clear();
+ }
+ }
+
+ /**
+ * Tests group operation failed interface.
+ */
+ @Test
+ public void testGroupOperationFailed() {
+ TestDelegate delegate = new TestDelegate();
+ groupStore.setDelegate(delegate);
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+ groupStore.deviceInitialAuditCompleted(deviceId2, true);
+
+ groupStore.storeGroupDescription(groupDescription1);
+ groupStore.storeGroupDescription(groupDescription2);
+
+ List<GroupEvent> eventsAfterAdds = delegate.eventsSeen();
+ assertThat(eventsAfterAdds, hasSize(2));
+ eventsAfterAdds.stream().forEach(event -> assertThat(event.type(), is(GroupEvent.Type.GROUP_ADD_REQUESTED)));
+ delegate.resetEvents();
+
+ GroupOperation opAdd =
+ GroupOperation.createAddGroupOperation(groupId1,
+ GroupDescription.Type.INDIRECT,
+ buckets);
+ groupStore.groupOperationFailed(deviceId1, opAdd);
+
+ List<GroupEvent> eventsAfterAddFailed = delegate.eventsSeen();
+ assertThat(eventsAfterAddFailed, hasSize(2));
+ assertThat(eventsAfterAddFailed.get(0).type(),
+ is(GroupEvent.Type.GROUP_ADD_FAILED));
+ assertThat(eventsAfterAddFailed.get(1).type(),
+ is(GroupEvent.Type.GROUP_REMOVED));
+ delegate.resetEvents();
+
+ GroupOperation opModify =
+ GroupOperation.createModifyGroupOperation(groupId2,
+ GroupDescription.Type.INDIRECT,
+ buckets);
+ groupStore.groupOperationFailed(deviceId2, opModify);
+ List<GroupEvent> eventsAfterModifyFailed = delegate.eventsSeen();
+ assertThat(eventsAfterModifyFailed, hasSize(1));
+ assertThat(eventsAfterModifyFailed.get(0).type(),
+ is(GroupEvent.Type.GROUP_UPDATE_FAILED));
+ delegate.resetEvents();
+
+ GroupOperation opDelete =
+ GroupOperation.createDeleteGroupOperation(groupId2,
+ GroupDescription.Type.INDIRECT);
+ groupStore.groupOperationFailed(deviceId2, opDelete);
+ List<GroupEvent> eventsAfterDeleteFailed = delegate.eventsSeen();
+ assertThat(eventsAfterDeleteFailed, hasSize(1));
+ assertThat(eventsAfterDeleteFailed.get(0).type(),
+ is(GroupEvent.Type.GROUP_REMOVE_FAILED));
+ delegate.resetEvents();
+ }
+
+ /**
+ * Tests extraneous group operations.
+ */
+ @Test
+ public void testExtraneousOperations() {
+ ArrayList<Group> extraneous;
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+
+ groupStore.storeGroupDescription(groupDescription1);
+ Group group1 = groupStore.getGroup(deviceId1, groupId1);
+
+ extraneous = Lists.newArrayList(groupStore.getExtraneousGroups(deviceId1));
+ assertThat(extraneous, hasSize(0));
+
+ groupStore.addOrUpdateExtraneousGroupEntry(group1);
+ extraneous = Lists.newArrayList(groupStore.getExtraneousGroups(deviceId1));
+ assertThat(extraneous, hasSize(1));
+
+ groupStore.removeExtraneousGroupEntry(group1);
+ extraneous = Lists.newArrayList(groupStore.getExtraneousGroups(deviceId1));
+ assertThat(extraneous, hasSize(0));
+ }
+
+ /**
+ * Tests updating of group descriptions.
+ */
+ @Test
+ public void testUpdateGroupDescription() {
+
+ GroupBuckets buckets =
+ new GroupBuckets(ImmutableList.of(failoverGroupBucket));
+
+ groupStore.deviceInitialAuditCompleted(deviceId1, true);
+ groupStore.storeGroupDescription(groupDescription1);
+
+ GroupKey newKey = new DefaultGroupKey("123".getBytes());
+ groupStore.updateGroupDescription(deviceId1,
+ groupKey1,
+ GroupStore.UpdateType.ADD,
+ buckets,
+ newKey);
+ Group group1 = groupStore.getGroup(deviceId1, groupId1);
+ assertThat(group1.appCookie(), is(newKey));
+ assertThat(group1.buckets().buckets(), hasSize(2));
+ }
+
+ @Test
+ public void testEqualsGroupStoreIdMapKey() {
+ DistributedGroupStore.GroupStoreIdMapKey key1 =
+ new DistributedGroupStore.GroupStoreIdMapKey(deviceId1, groupId1);
+ DistributedGroupStore.GroupStoreIdMapKey sameAsKey1 =
+ new DistributedGroupStore.GroupStoreIdMapKey(deviceId1, groupId1);
+ DistributedGroupStore.GroupStoreIdMapKey key2 =
+ new DistributedGroupStore.GroupStoreIdMapKey(deviceId2, groupId1);
+ DistributedGroupStore.GroupStoreIdMapKey key3 =
+ new DistributedGroupStore.GroupStoreIdMapKey(deviceId1, groupId2);
+
+ new EqualsTester()
+ .addEqualityGroup(key1, sameAsKey1)
+ .addEqualityGroup(key2)
+ .addEqualityGroup(key3)
+ .testEquals();
+ }
+
+ @Test
+ public void testEqualsGroupStoreKeyMapKey() {
+ DistributedGroupStore.GroupStoreKeyMapKey key1 =
+ new DistributedGroupStore.GroupStoreKeyMapKey(deviceId1, groupKey1);
+ DistributedGroupStore.GroupStoreKeyMapKey sameAsKey1 =
+ new DistributedGroupStore.GroupStoreKeyMapKey(deviceId1, groupKey1);
+ DistributedGroupStore.GroupStoreKeyMapKey key2 =
+ new DistributedGroupStore.GroupStoreKeyMapKey(deviceId2, groupKey1);
+ DistributedGroupStore.GroupStoreKeyMapKey key3 =
+ new DistributedGroupStore.GroupStoreKeyMapKey(deviceId1, groupKey2);
+
+ new EqualsTester()
+ .addEqualityGroup(key1, sameAsKey1)
+ .addEqualityGroup(key2)
+ .addEqualityGroup(key3)
+ .testEquals();
+ }
+
+ @Test
+ public void testEqualsGroupStoreMapKey() {
+ DistributedGroupStore.GroupStoreMapKey key1 =
+ new DistributedGroupStore.GroupStoreMapKey(deviceId1);
+ DistributedGroupStore.GroupStoreMapKey sameAsKey1 =
+ new DistributedGroupStore.GroupStoreMapKey(deviceId1);
+ DistributedGroupStore.GroupStoreMapKey key2 =
+ new DistributedGroupStore.GroupStoreMapKey(deviceId2);
+ DistributedGroupStore.GroupStoreMapKey key3 =
+ new DistributedGroupStore.GroupStoreMapKey(did("dev3"));
+
+ new EqualsTester()
+ .addEqualityGroup(key1, sameAsKey1)
+ .addEqualityGroup(key2)
+ .addEqualityGroup(key3)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/MastershipBasedTimestampTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/MastershipBasedTimestampTest.java
new file mode 100644
index 00000000..eb9d324e
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/MastershipBasedTimestampTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.store.impl;
+
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.serializers.custom.MastershipBasedTimestampSerializer;
+import org.onlab.util.KryoNamespace;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Test of {@link MastershipBasedTimestamp}.
+ */
+public class MastershipBasedTimestampTest {
+
+ private static final Timestamp TS_1_1 = new MastershipBasedTimestamp(1, 1);
+ private static final Timestamp TS_1_2 = new MastershipBasedTimestamp(1, 2);
+ private static final Timestamp TS_2_1 = new MastershipBasedTimestamp(2, 1);
+ private static final Timestamp TS_2_2 = new MastershipBasedTimestamp(2, 2);
+
+ @Test
+ public final void testBasic() {
+ final int termNumber = 5;
+ final int sequenceNumber = 6;
+ MastershipBasedTimestamp ts = new MastershipBasedTimestamp(termNumber,
+ sequenceNumber);
+
+ assertEquals(termNumber, ts.termNumber());
+ assertEquals(sequenceNumber, ts.sequenceNumber());
+ }
+
+ @Test
+ public final void testCompareTo() {
+ assertTrue(TS_1_1.compareTo(TS_1_1) == 0);
+ assertTrue(TS_1_1.compareTo(new MastershipBasedTimestamp(1, 1)) == 0);
+
+ assertTrue(TS_1_1.compareTo(TS_1_2) < 0);
+ assertTrue(TS_1_2.compareTo(TS_1_1) > 0);
+
+ assertTrue(TS_1_2.compareTo(TS_2_1) < 0);
+ assertTrue(TS_1_2.compareTo(TS_2_2) < 0);
+ assertTrue(TS_2_1.compareTo(TS_1_1) > 0);
+ assertTrue(TS_2_2.compareTo(TS_1_1) > 0);
+ }
+
+ @Test
+ public final void testEqualsObject() {
+ new EqualsTester()
+ .addEqualityGroup(new MastershipBasedTimestamp(1, 1),
+ new MastershipBasedTimestamp(1, 1), TS_1_1)
+ .addEqualityGroup(new MastershipBasedTimestamp(1, 2),
+ new MastershipBasedTimestamp(1, 2), TS_1_2)
+ .addEqualityGroup(new MastershipBasedTimestamp(2, 1),
+ new MastershipBasedTimestamp(2, 1), TS_2_1)
+ .addEqualityGroup(new MastershipBasedTimestamp(2, 2),
+ new MastershipBasedTimestamp(2, 2), TS_2_2)
+ .testEquals();
+ }
+
+ @Test
+ public final void testKryoSerializable() {
+ final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+ final KryoNamespace kryos = KryoNamespace.newBuilder()
+ .register(MastershipBasedTimestamp.class)
+ .build();
+
+ kryos.serialize(TS_2_1, buffer);
+ buffer.flip();
+ Timestamp copy = kryos.deserialize(buffer);
+
+ new EqualsTester()
+ .addEqualityGroup(TS_2_1, copy)
+ .testEquals();
+ }
+
+ @Test
+ public final void testKryoSerializableWithHandcraftedSerializer() {
+ final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+ final KryoNamespace kryos = KryoNamespace.newBuilder()
+ .register(new MastershipBasedTimestampSerializer(), MastershipBasedTimestamp.class)
+ .build();
+
+ kryos.serialize(TS_1_2, buffer);
+ buffer.flip();
+ Timestamp copy = kryos.deserialize(buffer);
+
+ new EqualsTester()
+ .addEqualityGroup(TS_1_2, copy)
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/TimestampedTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/TimestampedTest.java
new file mode 100644
index 00000000..0f67572f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/impl/TimestampedTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.store.impl;
+
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onosproject.store.Timestamp;
+import org.onlab.util.KryoNamespace;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Test of {@link Timestamped}.
+ */
+public class TimestampedTest {
+
+ private static final Timestamp TS_1_1 = new MastershipBasedTimestamp(1, 1);
+ private static final Timestamp TS_1_2 = new MastershipBasedTimestamp(1, 2);
+ private static final Timestamp TS_2_1 = new MastershipBasedTimestamp(2, 1);
+
+ @Test
+ public final void testHashCode() {
+ Timestamped<String> a = new Timestamped<>("a", TS_1_1);
+ Timestamped<String> b = new Timestamped<>("b", TS_1_1);
+ assertTrue("value does not impact hashCode",
+ a.hashCode() == b.hashCode());
+ }
+
+ @Test
+ public final void testEquals() {
+ Timestamped<String> a = new Timestamped<>("a", TS_1_1);
+ Timestamped<String> b = new Timestamped<>("b", TS_1_1);
+ assertTrue("value does not impact equality",
+ a.equals(b));
+
+ new EqualsTester()
+ .addEqualityGroup(new Timestamped<>("a", TS_1_1),
+ new Timestamped<>("b", TS_1_1),
+ new Timestamped<>("c", TS_1_1))
+ .addEqualityGroup(new Timestamped<>("a", TS_1_2),
+ new Timestamped<>("b", TS_1_2),
+ new Timestamped<>("c", TS_1_2))
+ .addEqualityGroup(new Timestamped<>("a", TS_2_1),
+ new Timestamped<>("b", TS_2_1),
+ new Timestamped<>("c", TS_2_1))
+ .testEquals();
+
+ }
+
+ @Test
+ public final void testValue() {
+ final Integer n = Integer.valueOf(42);
+ Timestamped<Integer> tsv = new Timestamped<>(n, TS_1_1);
+ assertSame(n, tsv.value());
+
+ }
+
+ @Test(expected = NullPointerException.class)
+ public final void testValueNonNull() {
+ new Timestamped<>(null, TS_1_1);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public final void testTimestampNonNull() {
+ new Timestamped<>("Foo", null);
+ }
+
+ @Test
+ public final void testIsNewer() {
+ Timestamped<String> a = new Timestamped<>("a", TS_1_2);
+ Timestamped<String> b = new Timestamped<>("b", TS_1_1);
+ assertTrue(a.isNewer(b));
+ assertFalse(b.isNewer(a));
+ }
+
+ @Test
+ public final void testKryoSerializable() {
+ final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+ final KryoNamespace kryos = KryoNamespace.newBuilder()
+ .register(Timestamped.class,
+ MastershipBasedTimestamp.class)
+ .build();
+
+ Timestamped<String> original = new Timestamped<>("foobar", TS_1_1);
+ kryos.serialize(original, buffer);
+ buffer.flip();
+ Timestamped<String> copy = kryos.deserialize(buffer);
+
+ new EqualsTester()
+ .addEqualityGroup(original, copy)
+ .testEquals();
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/GossipIntentStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/GossipIntentStoreTest.java
new file mode 100644
index 00000000..a74c3a2f
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/GossipIntentStoreTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.store.intent.impl;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.intent.PartitionServiceAdapter;
+import org.onosproject.store.service.TestStorageService;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.net.NetTestTools.hid;
+
+/**
+ * Gossip Intent Store test using database adapter.
+ */
+public class GossipIntentStoreTest {
+
+ private GossipIntentStore intentStore;
+ private IdGenerator idGenerator;
+ private HostToHostIntent.Builder builder1;
+
+ @Before
+ public void setUp() {
+ intentStore = new GossipIntentStore();
+ intentStore.storageService = new TestStorageService();
+ intentStore.partitionService = new PartitionServiceAdapter();
+ intentStore.clusterService = new ClusterServiceAdapter();
+ idGenerator = new MockIdGenerator();
+ Intent.bindIdGenerator(idGenerator);
+ builder1 = HostToHostIntent
+ .builder()
+ .one(hid("12:34:56:78:91:ab/1"))
+ .two(hid("12:34:56:78:91:ac/1"))
+ .appId(APP_ID);
+ intentStore.activate();
+ }
+
+ @After
+ public void cleanUp() {
+ intentStore.deactivate();
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * Generates a list of test intent data.
+ *
+ * @param count how many intent data objects are needed
+ * @return list of intent data
+ */
+ private List<IntentData> generateIntentList(int count) {
+ LinkedList<IntentData> intents = new LinkedList<>();
+ IntStream.rangeClosed(1, count)
+ .forEach(i ->
+ intents.add(
+ new IntentData(
+ builder1
+ .priority(i)
+ .build(),
+ IntentState.INSTALLED,
+ new IntentTestsMocks.MockTimestamp(12))));
+ return intents;
+ }
+
+ /**
+ * Tests the intent count APIs.
+ */
+ @Test
+ public void testGetIntentCount() {
+ assertThat(intentStore.getIntentCount(), is(0L));
+
+ generateIntentList(5).forEach(intentStore::write);
+
+ assertThat(intentStore.getIntentCount(), is(5L));
+ }
+
+ /**
+ * Tests the batch add API.
+ */
+ @Test
+ public void testBatchAdd() {
+ assertThat(intentStore.getIntentCount(), is(0L));
+
+ List<IntentData> intents = generateIntentList(5);
+
+ intentStore.batchWrite(intents);
+ assertThat(intentStore.getIntentCount(), is(5L));
+ }
+
+
+ /**
+ * Tests adding and withdrawing an Intent.
+ */
+ @Test
+ public void testAddAndWithdrawIntent() {
+ // build and install one intent
+ Intent intent = builder1.build();
+ IntentData installed = new IntentData(
+ intent,
+ IntentState.INSTALLED,
+ new IntentTestsMocks.MockTimestamp(12));
+ intentStore.write(installed);
+
+ // check that the intent count includes the new one
+ assertThat(intentStore.getIntentCount(), is(1L));
+
+ // check that the getIntents() API returns the new intent
+ intentStore.getIntents()
+ .forEach(item -> assertThat(item, is(intent)));
+
+ // check that the getInstallableIntents() API returns the new intent
+ intentStore.getInstallableIntents(intent.key())
+ .forEach(item -> assertThat(item, is(intent)));
+
+ // check that the getIntent() API can find the new intent
+ Intent queried = intentStore.getIntent(intent.key());
+ assertThat(queried, is(intent));
+
+ // check that the state of the new intent is correct
+ IntentState state = intentStore.getIntentState(intent.key());
+ assertThat(state, is(IntentState.INSTALLED));
+
+ // check that the getIntentData() API returns the proper value for the
+ // new intent
+ IntentData dataByQuery = intentStore.getIntentData(intent.key());
+ assertThat(dataByQuery, is(installed));
+
+ // check that the getIntentData() API returns the new intent when given
+ // a time stamp to look for
+ Iterable<IntentData> dataIteratorByTime = intentStore.getIntentData(true, 10L);
+ assertThat(dataIteratorByTime.iterator().hasNext(), is(true));
+ dataIteratorByTime.forEach(
+ data -> assertThat(data, is(installed))
+ );
+
+ // check that the getIntentData() API returns the new intent when asked to
+ // find all intents
+ Iterable<IntentData> dataIteratorAll = intentStore.getIntentData(false, 0L);
+ assertThat(dataIteratorAll.iterator().hasNext(), is(true));
+ dataIteratorAll.forEach(
+ data -> assertThat(data, is(installed))
+ );
+
+ // now purge the intent that was created
+ IntentData purge = new IntentData(
+ intent,
+ IntentState.PURGE_REQ,
+ new IntentTestsMocks.MockTimestamp(12));
+ intentStore.write(purge);
+
+ // check that no intents are left
+ assertThat(intentStore.getIntentCount(), is(0L));
+
+ // check that a getIntent() operation on the key of the purged intent
+ // returns null
+ Intent queriedAfterWithdrawal = intentStore.getIntent(intent.key());
+ assertThat(queriedAfterWithdrawal, nullValue());
+ }
+
+ /**
+ * Tests the operation of the APIs for the pending map.
+ */
+ @Test
+ public void testPending() {
+ // crete a new intent and add it as pending
+ Intent intent = builder1.build();
+ IntentData installed = new IntentData(
+ intent,
+ IntentState.INSTALLED,
+ new IntentTestsMocks.MockTimestamp(11));
+ intentStore.addPending(installed);
+
+ // check that the getPending() API returns the new pending intent
+ Iterable<Intent> pendingIntentIteratorAll = intentStore.getPending();
+ assertThat(pendingIntentIteratorAll.iterator().hasNext(), is(true));
+ pendingIntentIteratorAll.forEach(
+ data -> assertThat(data, is(intent))
+ );
+
+ // check that the getPendingData() API returns the IntentData for the
+ // new pending intent
+ Iterable<IntentData> pendingDataIteratorAll = intentStore.getPendingData();
+ assertThat(pendingDataIteratorAll.iterator().hasNext(), is(true));
+ pendingDataIteratorAll.forEach(
+ data -> assertThat(data, is(installed))
+ );
+
+ // check that the new pending intent is returned by the getPendingData()
+ // API when a time stamp is provided
+ Iterable<IntentData> pendingDataIteratorSelected =
+ intentStore.getPendingData(true, 10L);
+ assertThat(pendingDataIteratorSelected.iterator().hasNext(), is(true));
+ pendingDataIteratorSelected.forEach(
+ data -> assertThat(data, is(installed))
+ );
+
+ // check that the new pending intent is returned by the getPendingData()
+ // API when a time stamp is provided
+ Iterable<IntentData> pendingDataIteratorAllFromTimestamp =
+ intentStore.getPendingData(false, 0L);
+ assertThat(pendingDataIteratorAllFromTimestamp.iterator().hasNext(), is(true));
+ pendingDataIteratorSelected.forEach(
+ data -> assertThat(data, is(installed))
+ );
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java
new file mode 100644
index 00000000..25e23d3a
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java
@@ -0,0 +1,329 @@
+/*
+ * 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.store.intent.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.NullScheduledExecutor;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.LeadershipServiceAdapter;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.net.intent.Key;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+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.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for the PartitionManager class.
+ */
+public class PartitionManagerTest {
+
+ private final LeadershipEvent event
+ = new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED,
+ new Leadership(ELECTION_PREFIX + "0",
+ MY_NODE_ID, 0, 0));
+
+ private static final NodeId MY_NODE_ID = new NodeId("local");
+ private static final NodeId OTHER_NODE_ID = new NodeId("other");
+ private static final NodeId INACTIVE_NODE_ID = new NodeId("inactive");
+
+ private static final String ELECTION_PREFIX = "intent-partition-";
+
+ private LeadershipService leadershipService;
+ private LeadershipEventListener leaderListener;
+
+ private PartitionManager partitionManager;
+
+ @Before
+ public void setUp() {
+ leadershipService = createMock(LeadershipService.class);
+
+ leadershipService.addListener(anyObject(LeadershipEventListener.class));
+ expectLastCall().andDelegateTo(new TestLeadershipService());
+ for (int i = 0; i < PartitionManager.NUM_PARTITIONS; i++) {
+ expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
+ .andReturn(CompletableFuture.completedFuture(null))
+ .times(1);
+ }
+
+ partitionManager = new PartitionManager()
+ .withScheduledExecutor(new NullScheduledExecutor());
+
+ partitionManager.clusterService = new TestClusterService();
+ partitionManager.leadershipService = leadershipService;
+ partitionManager.eventDispatcher = new TestEventDispatcher();
+ }
+
+ /**
+ * Configures a mock leadership service to have the specified number of
+ * partitions owned by the local node and all other partitions owned by a
+ * (fake) remote node.
+ *
+ * @param numMine number of partitions that should be owned by the local node
+ */
+ private void setUpLeadershipService(int numMine) {
+
+ Map<String, Leadership> leaderBoard = new HashMap<>();
+
+ for (int i = 0; i < numMine; i++) {
+ expect(leadershipService.getLeader(ELECTION_PREFIX + i))
+ .andReturn(MY_NODE_ID).anyTimes();
+ leaderBoard.put(ELECTION_PREFIX + i,
+ new Leadership(ELECTION_PREFIX + i, MY_NODE_ID, 0, 0));
+ }
+
+ for (int i = numMine; i < PartitionManager.NUM_PARTITIONS; i++) {
+ expect(leadershipService.getLeader(ELECTION_PREFIX + i))
+ .andReturn(OTHER_NODE_ID).anyTimes();
+
+ leaderBoard.put(ELECTION_PREFIX + i,
+ new Leadership(ELECTION_PREFIX + i, OTHER_NODE_ID, 0, 0));
+ }
+
+ expect(leadershipService.getLeaderBoard()).andReturn(leaderBoard).anyTimes();
+ }
+
+ /**
+ * Tests that the PartitionManager's activate method correctly runs for
+ * all the leader elections that it should.
+ */
+ @Test
+ public void testActivate() {
+ reset(leadershipService);
+
+ leadershipService.addListener(anyObject(LeadershipEventListener.class));
+
+ for (int i = 0; i < PartitionManager.NUM_PARTITIONS; i++) {
+ expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
+ .andReturn(CompletableFuture.completedFuture(null))
+ .times(1);
+ }
+
+ replay(leadershipService);
+
+ partitionManager.activate();
+
+ verify(leadershipService);
+ }
+
+ /**
+ * Tests that the isMine method returns the correct result based on the
+ * underlying leadership service data.
+ */
+ @Test
+ public void testIsMine() {
+ // We'll own only the first partition
+ setUpLeadershipService(1);
+ replay(leadershipService);
+
+ Key myKey = new ControllableHashKey(0);
+ Key notMyKey = new ControllableHashKey(1);
+
+ assertTrue(partitionManager.isMine(myKey));
+ assertFalse(partitionManager.isMine(notMyKey));
+
+ // Make us the owner of 4 partitions now
+ reset(leadershipService);
+ setUpLeadershipService(4);
+ replay(leadershipService);
+
+ assertTrue(partitionManager.isMine(myKey));
+ // notMyKey is now my key because because we're in control of that
+ // partition now
+ assertTrue(partitionManager.isMine(notMyKey));
+
+ assertFalse(partitionManager.isMine(new ControllableHashKey(4)));
+ }
+
+ /**
+ * Tests sending in LeadershipServiceEvents in the case when we have
+ * too many partitions. The event will trigger the partition manager to
+ * schedule a rebalancing activity.
+ */
+ @Test
+ public void testRebalanceScheduling() {
+ // We have all the partitions so we'll need to relinquish some
+ setUpLeadershipService(PartitionManager.NUM_PARTITIONS);
+
+ replay(leadershipService);
+
+ partitionManager.activate();
+ // Send in the event
+ leaderListener.event(event);
+
+ assertTrue(partitionManager.rebalanceScheduled.get());
+
+ verify(leadershipService);
+ }
+
+ /**
+ * Tests rebalance will trigger the right now of leadership withdraw calls.
+ */
+ @Test
+ public void testRebalance() {
+ // We have all the partitions so we'll need to relinquish some
+ setUpLeadershipService(PartitionManager.NUM_PARTITIONS);
+
+ expect(leadershipService.withdraw(anyString()))
+ .andReturn(CompletableFuture.completedFuture(null))
+ .times(7);
+
+ replay(leadershipService);
+
+ partitionManager.activate();
+
+ // trigger rebalance
+ partitionManager.doRebalance();
+
+ verify(leadershipService);
+ }
+
+ /**
+ * Tests that attempts to rebalance when the paritions are already
+ * evenly distributed does not result in any relinquish attempts.
+ */
+ @Test
+ public void testNoRebalance() {
+ // Partitions are already perfectly balanced among the two active instances
+ setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2);
+ replay(leadershipService);
+
+ partitionManager.activate();
+
+ // trigger rebalance
+ partitionManager.doRebalance();
+
+ verify(leadershipService);
+
+ reset(leadershipService);
+ // We have a smaller share than we should
+ setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2 - 1);
+ replay(leadershipService);
+
+ // trigger rebalance
+ partitionManager.doRebalance();
+
+ verify(leadershipService);
+ }
+
+ /**
+ * LeadershipService that allows us to grab a reference to
+ * PartitionManager's LeadershipEventListener.
+ */
+ public class TestLeadershipService extends LeadershipServiceAdapter {
+ @Override
+ public void addListener(LeadershipEventListener listener) {
+ leaderListener = listener;
+ }
+ }
+
+ /**
+ * ClusterService set up with a very simple cluster - 3 nodes, one is the
+ * current node, one is a different active node, and one is an inactive node.
+ */
+ private class TestClusterService extends ClusterServiceAdapter {
+
+ private final ControllerNode self =
+ new DefaultControllerNode(MY_NODE_ID, IpAddress.valueOf(1));
+ private final ControllerNode otherNode =
+ new DefaultControllerNode(OTHER_NODE_ID, IpAddress.valueOf(2));
+ private final ControllerNode inactiveNode =
+ new DefaultControllerNode(INACTIVE_NODE_ID, IpAddress.valueOf(3));
+
+ Set<ControllerNode> nodes;
+
+ public TestClusterService() {
+ nodes = new HashSet<>();
+ nodes.add(self);
+ nodes.add(otherNode);
+ nodes.add(inactiveNode);
+ }
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return self;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return nodes;
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ return nodes.stream()
+ .filter(c -> c.id().equals(nodeId))
+ .findFirst()
+ .get();
+ }
+
+ @Override
+ public ControllerNode.State getState(NodeId nodeId) {
+ return nodeId.equals(INACTIVE_NODE_ID) ? ControllerNode.State.INACTIVE :
+ ControllerNode.State.ACTIVE;
+ }
+ }
+
+ /**
+ * A key that always hashes to a value provided to the constructor. This
+ * allows us to control the hash of the key for unit tests.
+ */
+ private class ControllableHashKey extends Key {
+
+ protected ControllableHashKey(long hash) {
+ super(hash);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(hash());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ControllableHashKey)) {
+ return false;
+ }
+
+ ControllableHashKey that = (ControllableHashKey) obj;
+
+ return Objects.equals(this.hash(), that.hash());
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/GossipLinkStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/GossipLinkStoreTest.java
new file mode 100644
index 00000000..bf7af464
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/GossipLinkStoreTest.java
@@ -0,0 +1,632 @@
+/*
+ * 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.store.link.impl;
+
+import com.google.common.collect.Iterables;
+
+import org.easymock.Capture;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DeviceClockService;
+import org.onosproject.net.device.DeviceClockServiceAdapter;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkStore;
+import org.onosproject.net.link.LinkStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.StaticClusterService;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.impl.MastershipBasedTimestamp;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.Link.Type.EDGE;
+import static org.onosproject.net.Link.Type.INDIRECT;
+import static org.onosproject.net.NetTestTools.assertAnnotationsEquals;
+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.link.LinkEvent.Type.LINK_UPDATED;
+
+/**
+ * Test of the GossipLinkStoreTest implementation.
+ */
+public class GossipLinkStoreTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+
+ 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 SparseAnnotations A1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .build();
+ private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+ .remove("A1")
+ .set("B3", "b3")
+ .build();
+ private static final SparseAnnotations A2 = DefaultAnnotations.builder()
+ .set("A2", "a2")
+ .set("B2", "b2")
+ .build();
+ private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
+ .remove("A2")
+ .set("B4", "b4")
+ .build();
+
+ // local node
+ private static final NodeId NID1 = new NodeId("local");
+ private static final ControllerNode ONOS1 =
+ new DefaultControllerNode(NID1, IpAddress.valueOf("127.0.0.1"));
+
+ // remote node
+ private static final NodeId NID2 = new NodeId("remote");
+ private static final ControllerNode ONOS2 =
+ new DefaultControllerNode(NID2, IpAddress.valueOf("127.0.0.2"));
+
+ private GossipLinkStore linkStoreImpl;
+ private LinkStore linkStore;
+
+ private final AtomicLong ticker = new AtomicLong();
+ private DeviceClockService deviceClockService;
+ private ClusterCommunicationService clusterCommunicator;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // TODO mock clusterCommunicator
+ clusterCommunicator = createNiceMock(ClusterCommunicationService.class);
+ clusterCommunicator.addSubscriber(anyObject(MessageSubject.class),
+ anyObject(ClusterMessageHandler.class),
+ anyObject(ExecutorService.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+
+ linkStoreImpl = new GossipLinkStore();
+ linkStoreImpl.deviceClockService = deviceClockService;
+ linkStoreImpl.clusterCommunicator = clusterCommunicator;
+ linkStoreImpl.clusterService = new TestClusterService();
+ linkStoreImpl.deviceClockService = new TestDeviceClockService();
+ linkStoreImpl.mastershipService = new TestMastershipService();
+ linkStoreImpl.activate();
+ linkStore = linkStoreImpl;
+
+ verify(clusterCommunicator);
+ reset(clusterCommunicator);
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ linkStoreImpl.deactivate();
+ }
+
+ private void putLink(DeviceId srcId, PortNumber srcNum,
+ DeviceId dstId, PortNumber dstNum, Type type,
+ SparseAnnotations... annotations) {
+ ConnectPoint src = new ConnectPoint(srcId, srcNum);
+ ConnectPoint dst = new ConnectPoint(dstId, dstNum);
+ reset(clusterCommunicator);
+ clusterCommunicator.<InternalLinkEvent>broadcast(
+ anyObject(InternalLinkEvent.class), anyObject(MessageSubject.class), anyObject(Function.class));
+ expectLastCall().anyTimes();
+ replay(clusterCommunicator);
+ linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type, annotations));
+ verify(clusterCommunicator);
+ }
+
+ private <T> void resetCommunicatorExpectingSingleBroadcast(
+ Capture<T> message,
+ Capture<MessageSubject> subject,
+ Capture<Function<T, byte[]>> encoder) {
+ message.reset();
+ subject.reset();
+ encoder.reset();
+ reset(clusterCommunicator);
+ clusterCommunicator.broadcast(capture(message), capture(subject), capture(encoder));
+ expectLastCall().once();
+ replay(clusterCommunicator);
+ }
+
+ private void putLink(LinkKey key, Type type, SparseAnnotations... annotations) {
+ putLink(key.src().deviceId(), key.src().port(),
+ key.dst().deviceId(), key.dst().port(),
+ type, annotations);
+ }
+
+ private static void assertLink(DeviceId srcId, PortNumber srcNum,
+ DeviceId dstId, PortNumber dstNum, Type type,
+ Link link) {
+ assertEquals(srcId, link.src().deviceId());
+ assertEquals(srcNum, link.src().port());
+ assertEquals(dstId, link.dst().deviceId());
+ assertEquals(dstNum, link.dst().port());
+ assertEquals(type, link.type());
+ }
+
+ private static void assertLink(LinkKey key, Type type, Link link) {
+ assertLink(key.src().deviceId(), key.src().port(),
+ key.dst().deviceId(), key.dst().port(),
+ type, link);
+ }
+
+ @Test
+ public final void testGetLinkCount() {
+ assertEquals("initialy empty", 0, linkStore.getLinkCount());
+
+ putLink(DID1, P1, DID2, P2, DIRECT);
+ putLink(DID2, P2, DID1, P1, DIRECT);
+ putLink(DID1, P1, DID2, P2, DIRECT);
+
+ assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
+ }
+
+ @Test
+ public final void testGetLinks() {
+ assertEquals("initialy empty", 0,
+ Iterables.size(linkStore.getLinks()));
+
+ LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
+ LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId1, DIRECT);
+
+ assertEquals("expecting 2 unique link", 2,
+ Iterables.size(linkStore.getLinks()));
+
+ Map<LinkKey, Link> links = new HashMap<>();
+ for (Link link : linkStore.getLinks()) {
+ links.put(LinkKey.linkKey(link), link);
+ }
+
+ assertLink(linkId1, DIRECT, links.get(linkId1));
+ assertLink(linkId2, DIRECT, links.get(linkId2));
+ }
+
+ @Test
+ public final void testGetDeviceEgressLinks() {
+ LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
+ LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getDeviceEgressLinks(DID1);
+ assertEquals(2, links1.size());
+ // check
+
+ Set<Link> links2 = linkStore.getDeviceEgressLinks(DID2);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testGetDeviceIngressLinks() {
+ LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
+ LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getDeviceIngressLinks(DID2);
+ assertEquals(2, links1.size());
+ // check
+
+ Set<Link> links2 = linkStore.getDeviceIngressLinks(DID1);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testGetLink() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(src, dst);
+
+ putLink(linkId1, DIRECT);
+
+ Link link = linkStore.getLink(src, dst);
+ assertLink(linkId1, DIRECT, link);
+
+ assertNull("There shouldn't be reverese link",
+ linkStore.getLink(dst, src));
+ }
+
+ @Test
+ public final void testGetEgressLinks() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getEgressLinks(d1P1);
+ assertEquals(1, links1.size());
+ assertLink(linkId1, DIRECT, links1.iterator().next());
+
+ Set<Link> links2 = linkStore.getEgressLinks(d2P2);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testGetIngressLinks() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+ LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
+
+ putLink(linkId1, DIRECT);
+ putLink(linkId2, DIRECT);
+ putLink(linkId3, DIRECT);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ Set<Link> links1 = linkStore.getIngressLinks(d2P2);
+ assertEquals(1, links1.size());
+ assertLink(linkId1, DIRECT, links1.iterator().next());
+
+ Set<Link> links2 = linkStore.getIngressLinks(d1P1);
+ assertEquals(1, links2.size());
+ assertLink(linkId2, DIRECT, links2.iterator().next());
+ }
+
+ @Test
+ public final void testCreateOrUpdateLink() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+ Capture<InternalLinkEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalLinkEvent, byte[]>> encoder = new Capture<>();
+
+ // add link
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ final DefaultLinkDescription linkDescription = new DefaultLinkDescription(src, dst, INDIRECT);
+ LinkEvent event = linkStore.createOrUpdateLink(PID,
+ linkDescription);
+ verifyLinkBroadcastMessage(PID, NID1, src, dst, INDIRECT, message, subject, encoder);
+
+ assertLink(DID1, P1, DID2, P2, INDIRECT, event.subject());
+ assertEquals(LINK_ADDED, event.type());
+
+ // update link type
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event2 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT));
+ verifyLinkBroadcastMessage(PID, NID1, src, dst, DIRECT, message, subject, encoder);
+
+ assertLink(DID1, P1, DID2, P2, DIRECT, event2.subject());
+ assertEquals(LINK_UPDATED, event2.type());
+
+ // no change
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event3 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT));
+ verifyNoBroadcastMessage(message);
+
+ assertNull("No change event expected", event3);
+ }
+
+ private <T> void verifyNoBroadcastMessage(Capture<T> message) {
+ assertFalse("No broadcast expected", message.hasCaptured());
+ }
+
+ private void verifyLinkBroadcastMessage(ProviderId providerId,
+ NodeId sender,
+ ConnectPoint src,
+ ConnectPoint dst,
+ Type type,
+ Capture<InternalLinkEvent> actualLinkEvent,
+ Capture<MessageSubject> actualSubject,
+ Capture<Function<InternalLinkEvent, byte[]>> actualEncoder) {
+ verify(clusterCommunicator);
+ assertTrue(actualLinkEvent.hasCaptured());
+ assertEquals(GossipLinkStoreMessageSubjects.LINK_UPDATE, actualSubject.getValue());
+ assertEquals(providerId, actualLinkEvent.getValue().providerId());
+ assertLinkDescriptionEquals(src, dst, type, actualLinkEvent.getValue().linkDescription().value());
+ }
+
+ private static void assertLinkDescriptionEquals(ConnectPoint src,
+ ConnectPoint dst,
+ Type type,
+ LinkDescription actual) {
+ assertEquals(src, actual.src());
+ assertEquals(dst, actual.dst());
+ assertEquals(type, actual.type());
+ // TODO check annotations
+ }
+
+ @Test
+ public final void testCreateOrUpdateLinkAncillary() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+ Capture<InternalLinkEvent> message = new Capture<>();
+ Capture<MessageSubject> subject = new Capture<>();
+ Capture<Function<InternalLinkEvent, byte[]>> encoder = new Capture<>();
+
+ // add Ancillary link
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event = linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, INDIRECT, A1));
+ verifyLinkBroadcastMessage(PIDA, NID1, src, dst, INDIRECT, message, subject, encoder);
+
+ assertNotNull("Ancillary only link is ignored", event);
+
+ // add Primary link
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event2 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, INDIRECT, A2));
+ verifyLinkBroadcastMessage(PID, NID1, src, dst, INDIRECT, message, subject, encoder);
+
+ assertLink(DID1, P1, DID2, P2, INDIRECT, event2.subject());
+ assertAnnotationsEquals(event2.subject().annotations(), A2, A1);
+ assertEquals(LINK_UPDATED, event2.type());
+
+ // update link type
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event3 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT, A2));
+ verifyLinkBroadcastMessage(PID, NID1, src, dst, DIRECT, message, subject, encoder);
+
+ assertLink(DID1, P1, DID2, P2, DIRECT, event3.subject());
+ assertAnnotationsEquals(event3.subject().annotations(), A2, A1);
+ assertEquals(LINK_UPDATED, event3.type());
+
+
+ // no change
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event4 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT));
+ verifyNoBroadcastMessage(message);
+
+ assertNull("No change event expected", event4);
+
+ // update link annotation (Primary)
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event5 = linkStore.createOrUpdateLink(PID,
+ new DefaultLinkDescription(src, dst, DIRECT, A2_2));
+ verifyLinkBroadcastMessage(PID, NID1, src, dst, DIRECT, message, subject, encoder);
+
+ assertLink(DID1, P1, DID2, P2, DIRECT, event5.subject());
+ assertAnnotationsEquals(event5.subject().annotations(), A2, A2_2, A1);
+ assertEquals(LINK_UPDATED, event5.type());
+
+ // update link annotation (Ancillary)
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event6 = linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, DIRECT, A1_2));
+ verifyLinkBroadcastMessage(PIDA, NID1, src, dst, DIRECT, message, subject, encoder);
+
+ assertLink(DID1, P1, DID2, P2, DIRECT, event6.subject());
+ assertAnnotationsEquals(event6.subject().annotations(), A2, A2_2, A1, A1_2);
+ assertEquals(LINK_UPDATED, event6.type());
+
+ // update link type (Ancillary) : ignored
+ resetCommunicatorExpectingSingleBroadcast(message, subject, encoder);
+ LinkEvent event7 = linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, EDGE));
+ verifyNoBroadcastMessage(message);
+ assertNull("Ancillary change other than annotation is ignored", event7);
+ }
+
+
+ @Test
+ public final void testRemoveLink() {
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+ LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+
+ putLink(linkId1, DIRECT, A1);
+ putLink(linkId2, DIRECT, A2);
+
+ // DID1,P1 => DID2,P2
+ // DID2,P2 => DID1,P1
+ // DID1,P2 => DID2,P3
+
+ LinkEvent event = linkStore.removeLink(d1P1, d2P2);
+ assertEquals(LINK_REMOVED, event.type());
+ assertAnnotationsEquals(event.subject().annotations(), A1);
+ LinkEvent event2 = linkStore.removeLink(d1P1, d2P2);
+ assertNull(event2);
+
+ assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
+ assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(), A2);
+
+ // annotations, etc. should not survive remove
+ putLink(linkId1, DIRECT);
+ assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2));
+ assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations());
+ }
+
+ @Test
+ public final void testAncillaryVisible() {
+ ConnectPoint src = new ConnectPoint(DID1, P1);
+ ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+ // add Ancillary link
+ linkStore.createOrUpdateLink(PIDA,
+ new DefaultLinkDescription(src, dst, INDIRECT, A1));
+
+ // Ancillary only link should not be visible
+ assertEquals(1, linkStore.getLinkCount());
+ assertNotNull(linkStore.getLink(src, dst));
+ }
+
+ // If Delegates should be called only on remote events,
+ // then Simple* should never call them, thus not test required.
+ @Ignore("Ignore until Delegate spec. is clear.")
+ @Test
+ public final void testEvents() throws InterruptedException {
+
+ final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+ final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+ final LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+
+ final CountDownLatch addLatch = new CountDownLatch(1);
+ LinkStoreDelegate checkAdd = new LinkStoreDelegate() {
+ @Override
+ public void notify(LinkEvent event) {
+ assertEquals(LINK_ADDED, event.type());
+ assertLink(linkId1, INDIRECT, event.subject());
+ addLatch.countDown();
+ }
+ };
+ final CountDownLatch updateLatch = new CountDownLatch(1);
+ LinkStoreDelegate checkUpdate = new LinkStoreDelegate() {
+ @Override
+ public void notify(LinkEvent event) {
+ assertEquals(LINK_UPDATED, event.type());
+ assertLink(linkId1, DIRECT, event.subject());
+ updateLatch.countDown();
+ }
+ };
+ final CountDownLatch removeLatch = new CountDownLatch(1);
+ LinkStoreDelegate checkRemove = new LinkStoreDelegate() {
+ @Override
+ public void notify(LinkEvent event) {
+ assertEquals(LINK_REMOVED, event.type());
+ assertLink(linkId1, DIRECT, event.subject());
+ removeLatch.countDown();
+ }
+ };
+
+ linkStore.setDelegate(checkAdd);
+ putLink(linkId1, INDIRECT);
+ assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+
+ linkStore.unsetDelegate(checkAdd);
+ linkStore.setDelegate(checkUpdate);
+ putLink(linkId1, DIRECT);
+ assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
+
+ linkStore.unsetDelegate(checkUpdate);
+ linkStore.setDelegate(checkRemove);
+ linkStore.removeLink(d1P1, d2P2);
+ assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
+ }
+
+ private static final class TestClusterService extends StaticClusterService {
+
+ public TestClusterService() {
+ localNode = ONOS1;
+ nodes.put(NID1, ONOS1);
+ nodeStates.put(NID1, ACTIVE);
+
+ nodes.put(NID2, ONOS2);
+ nodeStates.put(NID2, ACTIVE);
+ }
+ }
+
+ private final class TestDeviceClockService extends DeviceClockServiceAdapter {
+
+ private final AtomicLong ticker = new AtomicLong();
+
+ @Override
+ public Timestamp getTimestamp(DeviceId deviceId) {
+ if (DID1.equals(deviceId)) {
+ return new MastershipBasedTimestamp(1, ticker.getAndIncrement());
+ } else if (DID2.equals(deviceId)) {
+ return new MastershipBasedTimestamp(2, ticker.getAndIncrement());
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public boolean isTimestampAvailable(DeviceId deviceId) {
+ return DID1.equals(deviceId) || DID2.equals(deviceId);
+ }
+ }
+
+ private final class TestMastershipService extends MastershipServiceAdapter {
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ return NID1;
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/LinkFragmentIdTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/LinkFragmentIdTest.java
new file mode 100644
index 00000000..a14b6e60
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/link/impl/LinkFragmentIdTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.store.link.impl;
+
+import static org.onosproject.net.DeviceId.deviceId;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import com.google.common.testing.EqualsTester;
+
+public class LinkFragmentIdTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+
+ private static final DeviceId DID1 = deviceId("of:foo");
+ private static final DeviceId DID2 = deviceId("of:bar");
+
+ 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 ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+ private static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+
+ private static final ConnectPoint CP3 = new ConnectPoint(DID1, P2);
+ private static final ConnectPoint CP4 = new ConnectPoint(DID2, P3);
+
+ private static final LinkKey L1 = LinkKey.linkKey(CP1, CP2);
+ private static final LinkKey L2 = LinkKey.linkKey(CP3, CP4);
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(new LinkFragmentId(L1, PID),
+ new LinkFragmentId(L1, PID))
+ .addEqualityGroup(new LinkFragmentId(L2, PID),
+ new LinkFragmentId(L2, PID))
+ .addEqualityGroup(new LinkFragmentId(L1, PIDA),
+ new LinkFragmentId(L1, PIDA))
+ .addEqualityGroup(new LinkFragmentId(L2, PIDA),
+ new LinkFragmentId(L2, PIDA))
+ .testEquals();
+ }
+
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/DistributedMastershipStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/DistributedMastershipStoreTest.java
new file mode 100644
index 00000000..0b704011
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/DistributedMastershipStoreTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.store.mastership.impl;
+
+/**
+ * Test of the Hazelcast-based distributed MastershipStore implementation.
+ */
+public class DistributedMastershipStoreTest {
+/*
+ private static final DeviceId DID1 = DeviceId.deviceId("of:01");
+ private static final DeviceId DID2 = DeviceId.deviceId("of:02");
+ private static final DeviceId DID3 = DeviceId.deviceId("of:03");
+
+ private static final IpAddress IP = IpAddress.valueOf("127.0.0.1");
+
+ private static final NodeId N1 = new NodeId("node1");
+ private static final NodeId N2 = new NodeId("node2");
+
+ private static final ControllerNode CN1 = new DefaultControllerNode(N1, IP);
+ private static final ControllerNode CN2 = new DefaultControllerNode(N2, IP);
+
+ private DistributedMastershipStore dms;
+ private TestDistributedMastershipStore testStore;
+ private KryoSerializer serializationMgr;
+ private StoreManager storeMgr;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // TODO should find a way to clean Hazelcast instance without shutdown.
+ TestStoreManager testStoreMgr = new TestStoreManager();
+ testStoreMgr.setHazelcastInstance(testStoreMgr.initSingleInstance());
+ storeMgr = testStoreMgr;
+ storeMgr.activate();
+
+ serializationMgr = new KryoSerializer();
+
+ dms = new TestDistributedMastershipStore(storeMgr, serializationMgr);
+ dms.clusterService = new TestClusterService();
+ dms.activate();
+
+ testStore = (TestDistributedMastershipStore) dms;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ dms.deactivate();
+
+ storeMgr.deactivate();
+ }
+
+ @Test
+ @Ignore("Disabled this test due to intermittent failures seen on Jenkins runs")
+ public void getRole() {
+ assertEquals("wrong role:", NONE, dms.getRole(N1, DID1));
+ testStore.put(DID1, N1, true, false, true);
+ assertEquals("wrong role:", MASTER, dms.getRole(N1, DID1));
+ testStore.put(DID1, N2, false, true, false);
+ assertEquals("wrong role:", STANDBY, dms.getRole(N2, DID1));
+ }
+
+ @Test
+ public void getMaster() {
+ assertTrue("wrong store state:", dms.roleMap.isEmpty());
+
+ testStore.put(DID1, N1, true, false, false);
+ TestTools.assertAfter(100, () -> //wait for up to 100ms
+ assertEquals("wrong master:", N1, dms.getMaster(DID1)));
+ assertNull("wrong master:", dms.getMaster(DID2));
+ }
+
+ @Test
+ public void getDevices() {
+ assertTrue("wrong store state:", dms.roleMap.isEmpty());
+
+ testStore.put(DID1, N1, true, false, false);
+ testStore.put(DID2, N1, true, false, false);
+ testStore.put(DID3, N2, true, false, false);
+ assertEquals("wrong devices",
+ Sets.newHashSet(DID1, DID2), dms.getDevices(N1));
+ }
+
+ @Test
+ public void requestRoleAndTerm() {
+ //CN1 is "local"
+ testStore.setCurrent(CN1);
+
+ //if already MASTER, nothing should happen
+ testStore.put(DID2, N1, true, false, true);
+ assertEquals("wrong role for MASTER:", MASTER, Futures.getUnchecked(dms.requestRole(DID2)));
+
+ //populate maps with DID1, N1 thru NONE case
+ assertEquals("wrong role for NONE:", MASTER, Futures.getUnchecked(dms.requestRole(DID1)));
+ assertTrue("wrong state for store:", !dms.terms.isEmpty());
+ assertEquals("wrong term",
+ MastershipTerm.of(N1, 1), dms.getTermFor(DID1));
+
+ //CN2 now local. DID2 has N1 as MASTER so N2 is STANDBY
+ testStore.setCurrent(CN2);
+ assertEquals("wrong role for STANDBY:", STANDBY, Futures.getUnchecked(dms.requestRole(DID2)));
+ assertEquals("wrong number of entries:", 2, dms.terms.size());
+
+ //change term and requestRole() again; should persist
+ testStore.increment(DID2);
+ assertEquals("wrong role for STANDBY:", STANDBY, Futures.getUnchecked(dms.requestRole(DID2)));
+ assertEquals("wrong term", MastershipTerm.of(N1, 1), dms.getTermFor(DID2));
+ }
+
+ @Test
+ public void setMaster() {
+ //populate maps with DID1, N1 as MASTER thru NONE case
+ testStore.setCurrent(CN1);
+ assertEquals("wrong role for NONE:", MASTER, Futures.getUnchecked(dms.requestRole(DID1)));
+ assertNull("wrong event:", Futures.getUnchecked(dms.setMaster(N1, DID1)));
+
+ //switch over to N2
+ assertEquals("wrong event:", Type.MASTER_CHANGED, Futures.getUnchecked(dms.setMaster(N2, DID1)).type());
+ System.out.println(dms.getTermFor(DID1).master() + ":" + dms.getTermFor(DID1).termNumber());
+ assertEquals("wrong term", MastershipTerm.of(N2, 2), dms.getTermFor(DID1));
+
+ //orphan switch - should be rare case
+ assertEquals("wrong event:", Type.MASTER_CHANGED, Futures.getUnchecked(dms.setMaster(N2, DID2)).type());
+ assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID2));
+ //disconnect and reconnect - sign of failing re-election or single-instance channel
+ dms.roleMap.clear();
+ dms.setMaster(N2, DID2);
+ assertEquals("wrong term", MastershipTerm.of(N2, 2), dms.getTermFor(DID2));
+ }
+
+ @Test
+ public void relinquishRole() {
+ //populate maps with DID1, N1 as MASTER thru NONE case
+ testStore.setCurrent(CN1);
+ assertEquals("wrong role for NONE:", MASTER, Futures.getUnchecked(dms.requestRole(DID1)));
+ //no backup, no new MASTER/event
+ assertNull("wrong event:", Futures.getUnchecked(dms.relinquishRole(N1, DID1)));
+
+ dms.requestRole(DID1);
+
+ //add backup CN2, get it elected MASTER by relinquishing
+ testStore.setCurrent(CN2);
+ assertEquals("wrong role for NONE:", STANDBY, Futures.getUnchecked(dms.requestRole(DID1)));
+ assertEquals("wrong event:", Type.MASTER_CHANGED, Futures.getUnchecked(dms.relinquishRole(N1, DID1)).type());
+ assertEquals("wrong master", N2, dms.getMaster(DID1));
+
+ //all nodes "give up" on device, which goes back to NONE.
+ assertNull("wrong event:", Futures.getUnchecked(dms.relinquishRole(N2, DID1)));
+ assertEquals("wrong role for node:", NONE, dms.getRole(N2, DID1));
+
+ assertEquals("wrong number of retired nodes", 2,
+ dms.roleMap.get(DID1).nodesOfRole(NONE).size());
+
+ //bring nodes back
+ assertEquals("wrong role for NONE:", MASTER, Futures.getUnchecked(dms.requestRole(DID1)));
+ testStore.setCurrent(CN1);
+ assertEquals("wrong role for NONE:", STANDBY, Futures.getUnchecked(dms.requestRole(DID1)));
+ assertEquals("wrong number of backup nodes", 1,
+ dms.roleMap.get(DID1).nodesOfRole(STANDBY).size());
+
+ //If STANDBY, should drop to NONE
+ assertEquals("wrong event:", Type.BACKUPS_CHANGED, Futures.getUnchecked(dms.relinquishRole(N1, DID1)).type());
+ assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
+
+ //NONE - nothing happens
+ assertEquals("wrong event:", Type.BACKUPS_CHANGED, Futures.getUnchecked(dms.relinquishRole(N1, DID2)).type());
+ assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID2));
+
+ }
+
+ @Ignore("Ignore until Delegate spec. is clear.")
+ @Test
+ public void testEvents() throws InterruptedException {
+ //shamelessly copy other distributed store tests
+ final CountDownLatch addLatch = new CountDownLatch(1);
+
+ MastershipStoreDelegate checkAdd = new MastershipStoreDelegate() {
+ @Override
+ public void notify(MastershipEvent event) {
+ assertEquals("wrong event:", Type.MASTER_CHANGED, event.type());
+ assertEquals("wrong subject", DID1, event.subject());
+ assertEquals("wrong subject", N1, event.roleInfo().master());
+ addLatch.countDown();
+ }
+ };
+
+ dms.setDelegate(checkAdd);
+ dms.setMaster(N1, DID1);
+ //this will fail until we do something about single-instance-ness
+ assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+ }
+
+ private class TestDistributedMastershipStore extends
+ DistributedMastershipStore {
+ public TestDistributedMastershipStore(StoreService storeService,
+ KryoSerializer kryoSerialization) {
+ this.storeService = storeService;
+ this.serializer = kryoSerialization;
+ }
+
+ //helper to populate master/backup structures
+ public void put(DeviceId dev, NodeId node,
+ boolean master, boolean backup, boolean term) {
+ RoleValue rv = dms.roleMap.get(dev);
+ if (rv == null) {
+ rv = new RoleValue();
+ }
+
+ if (master) {
+ rv.add(MASTER, node);
+ rv.reassign(node, STANDBY, NONE);
+ }
+ if (backup) {
+ rv.add(STANDBY, node);
+ rv.remove(MASTER, node);
+ rv.remove(NONE, node);
+ }
+ if (term) {
+ dms.terms.put(dev, 0);
+ }
+ dms.roleMap.put(dev, rv);
+ }
+
+ //a dumb utility function.
+ public void dump() {
+ for (Map.Entry<DeviceId, RoleValue> el : dms.roleMap.entrySet()) {
+ System.out.println("DID: " + el.getKey());
+ for (MastershipRole role : MastershipRole.values()) {
+ System.out.println("\t" + role.toString() + ":");
+ for (NodeId n : el.getValue().nodesOfRole(role)) {
+ System.out.println("\t\t" + n);
+ }
+ }
+ }
+ }
+
+ //increment term for a device
+ public void increment(DeviceId dev) {
+ Integer t = dms.terms.get(dev);
+ if (t != null) {
+ dms.terms.put(dev, ++t);
+ }
+ }
+
+ //sets the "local" node
+ public void setCurrent(ControllerNode node) {
+ ((TestClusterService) clusterService).current = node;
+ }
+ }
+
+ private class TestClusterService extends ClusterServiceAdapter {
+
+ protected ControllerNode current;
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return current;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return Sets.newHashSet(CN1, CN2);
+ }
+
+ }
+*/
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/RoleValueTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/RoleValueTest.java
new file mode 100644
index 00000000..d3b5c764
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/mastership/impl/RoleValueTest.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.store.mastership.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.MastershipRole.*;
+
+import org.junit.Test;
+import org.onosproject.cluster.NodeId;
+
+import com.google.common.collect.Sets;
+
+public class RoleValueTest {
+
+ private static final RoleValue RV = new RoleValue();
+
+ private static final NodeId NID1 = new NodeId("node1");
+ private static final NodeId NID2 = new NodeId("node2");
+ private static final NodeId NID3 = new NodeId("node3");
+
+ @Test
+ public void add() {
+ assertEquals("faulty initialization: ", 3, RV.value.size());
+ RV.add(MASTER, NID1);
+ RV.add(STANDBY, NID2);
+ RV.add(STANDBY, NID3);
+
+ assertEquals("wrong nodeID: ", NID1, RV.get(MASTER));
+ assertTrue("wrong nodeIDs: ",
+ Sets.newHashSet(NID3, NID2).containsAll(RV.nodesOfRole(STANDBY)));
+ }
+}
diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/resource/impl/HazelcastLinkResourceStoreTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/resource/impl/HazelcastLinkResourceStoreTest.java
new file mode 100644
index 00000000..a5f2facb
--- /dev/null
+++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/resource/impl/HazelcastLinkResourceStoreTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.store.resource.impl;
+
+/**
+ * Test of the simple LinkResourceStore implementation.
+ */
+public class HazelcastLinkResourceStoreTest {
+/*
+ private LinkResourceStore store;
+ private HazelcastLinkResourceStore storeImpl;
+ private Link link1;
+ private Link link2;
+ private Link link3;
+ private TestStoreManager storeMgr;
+
+ /**
+ * Returns {@link Link} object.
+ *
+ * @param dev1 source device
+ * @param port1 source port
+ * @param dev2 destination device
+ * @param port2 destination port
+ * @return created {@link Link} object
+ * /
+ private Link newLink(String dev1, int port1, String dev2, int port2) {
+ Annotations annotations = DefaultAnnotations.builder()
+ .set(AnnotationKeys.OPTICAL_WAVES, "80")
+ .set(AnnotationKeys.BANDWIDTH, "1000")
+ .build();
+ return new DefaultLink(
+ new ProviderId("of", "foo"),
+ new ConnectPoint(deviceId(dev1), portNumber(port1)),
+ new ConnectPoint(deviceId(dev2), portNumber(port2)),
+ DIRECT, annotations);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+
+ TestStoreManager testStoreMgr = new TestStoreManager();
+ testStoreMgr.setHazelcastInstance(testStoreMgr.initSingleInstance());
+ storeMgr = testStoreMgr;
+ storeMgr.activate();
+
+
+ storeImpl = new TestHazelcastLinkResourceStore(storeMgr);
+ storeImpl.activate();
+ store = storeImpl;
+
+ link1 = newLink("of:1", 1, "of:2", 2);
+ link2 = newLink("of:2", 1, "of:3", 2);
+ link3 = newLink("of:3", 1, "of:4", 2);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ storeImpl.deactivate();
+
+ storeMgr.deactivate();
+ }
+
+ @Test
+ public void testConstructorAndActivate() {
+ final Iterable<LinkResourceAllocations> allAllocations = store.getAllocations();
+ assertNotNull(allAllocations);
+ assertFalse(allAllocations.iterator().hasNext());
+
+ final Iterable<LinkResourceAllocations> linkAllocations =
+ store.getAllocations(link1);
+ assertNotNull(linkAllocations);
+ assertFalse(linkAllocations.iterator().hasNext());
+
+ final Set<ResourceAllocation> res = store.getFreeResources(link2);
+ assertNotNull(res);
+ }
+
+ private BandwidthResourceAllocation getBandwidthObj(Set<ResourceAllocation> resources) {
+ for (ResourceAllocation res : resources) {
+ if (res.type() == ResourceType.BANDWIDTH) {
+ return ((BandwidthResourceAllocation) res);
+ }
+ }
+ return null;
+ }
+
+ private Set<LambdaResourceAllocation> getLambdaObjs(Set<ResourceAllocation> resources) {
+ Set<LambdaResourceAllocation> lambdaResources = new HashSet<>();
+ for (ResourceAllocation res : resources) {
+ if (res.type() == ResourceType.LAMBDA) {
+ lambdaResources.add((LambdaResourceAllocation) res);
+ }
+ }
+ return lambdaResources;
+ }
+
+ @Test
+ public void testInitialBandwidth() {
+ final Set<ResourceAllocation> freeRes = store.getFreeResources(link1);
+ assertNotNull(freeRes);
+
+ final BandwidthResourceAllocation alloc = getBandwidthObj(freeRes);
+ assertNotNull(alloc);
+
+ assertEquals(new BandwidthResource(Bandwidth.mbps(1000.0)), alloc.bandwidth());
+ }
+
+ @Test
+ public void testInitialLambdas() {
+ final Set<ResourceAllocation> freeRes = store.getFreeResources(link3);
+ assertNotNull(freeRes);
+
+ final Set<LambdaResourceAllocation> res = getLambdaObjs(freeRes);
+ assertNotNull(res);
+ assertEquals(80, res.size());
+ }
+
+ public static final class TestHazelcastLinkResourceStore
+ extends HazelcastLinkResourceStore {
+
+ public TestHazelcastLinkResourceStore(StoreService storeMgr) {
+ super.storeService = storeMgr;
+ }
+
+ }
+
+ @Test
+ public void testSuccessfulBandwidthAllocation() {
+ final Link link = newLink("of:1", 1, "of:2", 2);
+
+ final LinkResourceRequest request =
+ DefaultLinkResourceRequest.builder(IntentId.valueOf(1),
+ ImmutableSet.of(link))
+ .build();
+ final ResourceAllocation allocation =
+ new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.mbps(900.0)));
+ final Set<ResourceAllocation> allocationSet = ImmutableSet.of(allocation);
+
+ final LinkResourceAllocations allocations =
+ new DefaultLinkResourceAllocations(request, ImmutableMap.of(link, allocationSet));
+
+ store.allocateResources(allocations);
+ }
+
+ @Test
+ public void testUnsuccessfulBandwidthAllocation() {
+ final Link link = newLink("of:1", 1, "of:2", 2);
+
+ final LinkResourceRequest request =
+ DefaultLinkResourceRequest.builder(IntentId.valueOf(1),
+ ImmutableSet.of(link))
+ .build();
+ final ResourceAllocation allocation =
+ new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.mbps(9000.0)));
+ final Set<ResourceAllocation> allocationSet = ImmutableSet.of(allocation);
+
+ final LinkResourceAllocations allocations =
+ new DefaultLinkResourceAllocations(request, ImmutableMap.of(link, allocationSet));
+
+ boolean gotException = false;
+ try {
+ store.allocateResources(allocations);
+ } catch (ResourceAllocationException rae) {
+ assertEquals(true, rae.getMessage().contains("Unable to allocate bandwidth for link"));
+ gotException = true;
+ }
+ assertEquals(true, gotException);
+ }
+
+ @Test
+ public void testSuccessfulLambdaAllocation() {
+ final Link link = newLink("of:1", 1, "of:2", 2);
+
+ final LinkResourceRequest request =
+ DefaultLinkResourceRequest.builder(IntentId.valueOf(1),
+ ImmutableSet.of(link))
+ .build();
+ final ResourceAllocation allocation =
+ new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.mbps(900.0)));
+ final Set<ResourceAllocation> allocationSet = ImmutableSet.of(allocation);
+
+ final LinkResourceAllocations allocations =
+ new DefaultLinkResourceAllocations(request, ImmutableMap.of(link, allocationSet));
+
+ store.allocateResources(allocations);
+ }
+
+ @Test
+ public void testUnsuccessfulLambdaAllocation() {
+ final Link link = newLink("of:1", 1, "of:2", 2);
+
+ final LinkResourceRequest request =
+ DefaultLinkResourceRequest.builder(IntentId.valueOf(1),
+ ImmutableSet.of(link))
+ .build();
+ final ResourceAllocation allocation =
+ new LambdaResourceAllocation(LambdaResource.valueOf(33));
+ final Set<ResourceAllocation> allocationSet = ImmutableSet.of(allocation);
+
+ final LinkResourceAllocations allocations =
+ new DefaultLinkResourceAllocations(request, ImmutableMap.of(link, allocationSet));
+ store.allocateResources(allocations);
+
+ boolean gotException = false;
+ try {
+ store.allocateResources(allocations);
+ } catch (ResourceAllocationException rae) {
+ assertEquals(true, rae.getMessage().contains("Unable to allocate lambda for link"));
+ gotException = true;
+ }
+ assertEquals(true, gotException);
+ }
+ */
+}
diff --git a/framework/src/onos/core/store/pom.xml b/framework/src/onos/core/store/pom.xml
new file mode 100644
index 00000000..59d66642
--- /dev/null
+++ b/framework/src/onos/core/store/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core-store</artifactId>
+ <packaging>pom</packaging>
+
+ <description>ONOS Core Store subsystem</description>
+
+ <modules>
+ <module>dist</module>
+ <module>serializers</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/framework/src/onos/core/store/serializers/pom.xml b/framework/src/onos/core/store/serializers/pom.xml
new file mode 100644
index 00000000..d869f60c
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-store</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core-serializers</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Serializers for ONOS classes</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.esotericsoftware</groupId>
+ <artifactId>kryo</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-incubator-api</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/AnnotationsSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/AnnotationsSerializer.java
new file mode 100644
index 00000000..febb4adb
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/AnnotationsSerializer.java
@@ -0,0 +1,32 @@
+package org.onosproject.store.serializers;
+
+import org.onosproject.net.DefaultAnnotations;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+import java.util.HashMap;
+
+public class AnnotationsSerializer extends Serializer<DefaultAnnotations> {
+
+ public AnnotationsSerializer() {
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, DefaultAnnotations object) {
+ kryo.writeObject(output, object.asMap());
+ }
+
+ @Override
+ public DefaultAnnotations read(Kryo kryo, Input input, Class<DefaultAnnotations> type) {
+ DefaultAnnotations.Builder b = DefaultAnnotations.builder();
+ HashMap<String, String> map = kryo.readObject(input, HashMap.class);
+ map.forEach((k, v) -> b.set(k, v));
+
+ return b.build();
+ }
+
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ArraysAsListSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ArraysAsListSerializer.java
new file mode 100644
index 00000000..99c35310
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ArraysAsListSerializer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.store.serializers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link java.util.Arrays#asList(Object...)}.
+ */
+public final class ArraysAsListSerializer extends Serializer<List<?>> {
+
+ @Override
+ public void write(Kryo kryo, Output output, List<?> object) {
+ output.writeInt(object.size(), true);
+ for (Object elm : object) {
+ kryo.writeClassAndObject(output, elm);
+ }
+ }
+
+ @Override
+ public List<?> read(Kryo kryo, Input input, Class<List<?>> type) {
+ final int size = input.readInt(true);
+ List<Object> list = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ list.add(kryo.readClassAndObject(input));
+ }
+ return list;
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ConnectPointSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ConnectPointSerializer.java
new file mode 100644
index 00000000..f1027d5e
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ConnectPointSerializer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.store.serializers;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.PortNumber;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link ConnectPointSerializer}.
+ */
+public class ConnectPointSerializer extends Serializer<ConnectPoint> {
+
+ /**
+ * Creates {@link ConnectPointSerializer} serializer instance.
+ */
+ public ConnectPointSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, ConnectPoint object) {
+ kryo.writeClassAndObject(output, object.elementId());
+ kryo.writeClassAndObject(output, object.port());
+ }
+
+ @Override
+ public ConnectPoint read(Kryo kryo, Input input, Class<ConnectPoint> type) {
+ ElementId elementId = (ElementId) kryo.readClassAndObject(input);
+ PortNumber portNumber = (PortNumber) kryo.readClassAndObject(input);
+ return new ConnectPoint(elementId, portNumber);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultApplicationIdSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultApplicationIdSerializer.java
new file mode 100644
index 00000000..0b1ad957
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultApplicationIdSerializer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.store.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.onosproject.core.DefaultApplicationId;
+
+/**
+ * Kryo Serializer for {@link org.onosproject.core.DefaultApplicationId}.
+ */
+public final class DefaultApplicationIdSerializer extends Serializer<DefaultApplicationId> {
+
+ /**
+ * Creates {@link org.onosproject.core.DefaultApplicationId} serializer instance.
+ */
+ public DefaultApplicationIdSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, DefaultApplicationId object) {
+ kryo.writeObject(output, object.id());
+ kryo.writeObject(output, object.name());
+ }
+
+ @Override
+ public DefaultApplicationId read(Kryo kryo, Input input, Class<DefaultApplicationId> type) {
+ short id = kryo.readObject(input, Short.class);
+ String name = kryo.readObject(input, String.class);
+ return new DefaultApplicationId(id, name);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultLinkSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultLinkSerializer.java
new file mode 100644
index 00000000..6cc90667
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultLinkSerializer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.store.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link.State;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Kryo Serializer for {@link DefaultLink}.
+ */
+public class DefaultLinkSerializer extends Serializer<DefaultLink> {
+
+ /**
+ * Creates {@link DefaultLink} serializer instance.
+ */
+ public DefaultLinkSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, DefaultLink object) {
+ kryo.writeClassAndObject(output, object.providerId());
+ kryo.writeClassAndObject(output, object.src());
+ kryo.writeClassAndObject(output, object.dst());
+ kryo.writeClassAndObject(output, object.type());
+ kryo.writeClassAndObject(output, object.state());
+ output.writeBoolean(object.isDurable());
+ }
+
+ @Override
+ public DefaultLink read(Kryo kryo, Input input, Class<DefaultLink> type) {
+ ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+ ConnectPoint src = (ConnectPoint) kryo.readClassAndObject(input);
+ ConnectPoint dst = (ConnectPoint) kryo.readClassAndObject(input);
+ Type linkType = (Type) kryo.readClassAndObject(input);
+ State state = (State) kryo.readClassAndObject(input);
+ boolean isDurable = input.readBoolean();
+ return new DefaultLink(providerId, src, dst, linkType, state, isDurable);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultOutboundPacketSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultOutboundPacketSerializer.java
new file mode 100644
index 00000000..9d12e458
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultOutboundPacketSerializer.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.store.serializers;
+
+import java.nio.ByteBuffer;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Serializer for a default outbound packet.
+ */
+public class DefaultOutboundPacketSerializer extends Serializer<DefaultOutboundPacket> {
+
+ /**
+ * Creates {@link DefaultOutboundPacket} serializer instance.
+ */
+ public DefaultOutboundPacketSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public DefaultOutboundPacket read(Kryo kryo, Input input,
+ Class<DefaultOutboundPacket> type) {
+ DeviceId sendThrough = (DeviceId) kryo.readClassAndObject(input);
+ TrafficTreatment treatment = (TrafficTreatment) kryo.readClassAndObject(input);
+ byte[] data = (byte[]) kryo.readClassAndObject(input);
+ return new DefaultOutboundPacket(sendThrough, treatment, ByteBuffer.wrap(data));
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, DefaultOutboundPacket object) {
+ kryo.writeClassAndObject(output, object.sendThrough());
+ kryo.writeClassAndObject(output, object.treatment());
+ kryo.writeClassAndObject(output, object.data().array());
+ }
+
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultPortSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultPortSerializer.java
new file mode 100644
index 00000000..60d09fbe
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DefaultPortSerializer.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.store.serializers;
+
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Element;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link DefaultPort}.
+ */
+public final class DefaultPortSerializer extends
+ Serializer<DefaultPort> {
+
+ /**
+ * Creates {@link DefaultPort} serializer instance.
+ */
+ public DefaultPortSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, DefaultPort object) {
+ kryo.writeClassAndObject(output, object.element());
+ kryo.writeObject(output, object.number());
+ output.writeBoolean(object.isEnabled());
+ kryo.writeObject(output, object.type());
+ output.writeLong(object.portSpeed());
+ kryo.writeClassAndObject(output, object.annotations());
+ }
+
+ @Override
+ public DefaultPort read(Kryo kryo, Input input, Class<DefaultPort> aClass) {
+ Element element = (Element) kryo.readClassAndObject(input);
+ PortNumber number = kryo.readObject(input, PortNumber.class);
+ boolean isEnabled = input.readBoolean();
+ Port.Type type = kryo.readObject(input, Port.Type.class);
+ long portSpeed = input.readLong();
+ Annotations annotations = (Annotations) kryo.readClassAndObject(input);
+
+ return new DefaultPort(element, number, isEnabled, type, portSpeed, annotations);
+ }
+
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DeviceIdSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DeviceIdSerializer.java
new file mode 100644
index 00000000..a74a7695
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/DeviceIdSerializer.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.store.serializers;
+
+import org.onosproject.net.DeviceId;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+* Kryo Serializer for {@link DeviceId}.
+*/
+public final class DeviceIdSerializer extends Serializer<DeviceId> {
+
+ /**
+ * Creates {@link DeviceId} serializer instance.
+ */
+ public DeviceIdSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, DeviceId object) {
+ output.writeString(object.toString());
+ }
+
+ @Override
+ public DeviceId read(Kryo kryo, Input input, Class<DeviceId> type) {
+ final String str = input.readString();
+ return DeviceId.deviceId(str);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/HostLocationSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/HostLocationSerializer.java
new file mode 100644
index 00000000..270eb218
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/HostLocationSerializer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.store.serializers;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+* Kryo Serializer for {@link HostLocation}.
+*/
+public class HostLocationSerializer extends Serializer<HostLocation> {
+
+ /**
+ * Creates {@link HostLocation} serializer instance.
+ */
+ public HostLocationSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, HostLocation object) {
+ kryo.writeClassAndObject(output, object.deviceId());
+ kryo.writeClassAndObject(output, object.port());
+ output.writeLong(object.time());
+ }
+
+ @Override
+ public HostLocation read(Kryo kryo, Input input, Class<HostLocation> type) {
+ DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+ PortNumber portNumber = (PortNumber) kryo.readClassAndObject(input);
+ long time = input.readLong();
+ return new HostLocation(deviceId, portNumber, time);
+ }
+
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableListSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableListSerializer.java
new file mode 100644
index 00000000..95166c3b
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableListSerializer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.store.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+
+/**
+ * Creates {@link ImmutableList} serializer instance.
+ */
+public class ImmutableListSerializer extends Serializer<ImmutableList<?>> {
+
+ /**
+ * Creates {@link ImmutableList} serializer instance.
+ */
+ public ImmutableListSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+ @Override
+ public void write(Kryo kryo, Output output, ImmutableList<?> object) {
+ output.writeInt(object.size());
+ for (Object e : object) {
+ kryo.writeClassAndObject(output, e);
+ }
+ }
+
+ @Override
+ public ImmutableList<?> read(Kryo kryo, Input input,
+ Class<ImmutableList<?>> type) {
+ final int size = input.readInt();
+ Builder<Object> builder = ImmutableList.builder();
+ for (int i = 0; i < size; ++i) {
+ builder.add(kryo.readClassAndObject(input));
+ }
+ return builder.build();
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableMapSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableMapSerializer.java
new file mode 100644
index 00000000..4d6af788
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableMapSerializer.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.store.serializers;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.serializers.MapSerializer;
+import com.google.common.collect.ImmutableMap;
+
+/**
+* Kryo Serializer for {@link ImmutableMap}.
+*/
+public class ImmutableMapSerializer extends Serializer<ImmutableMap<?, ?>> {
+
+ private final MapSerializer mapSerializer = new MapSerializer();
+
+ /**
+ * Creates {@link ImmutableMap} serializer instance.
+ */
+ public ImmutableMapSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, ImmutableMap<?, ?> object) {
+ // wrapping with unmodifiableMap proxy
+ // to avoid Kryo from writing only the reference marker of this instance,
+ // which will be embedded right before this method call.
+ kryo.writeObject(output, Collections.unmodifiableMap(object), mapSerializer);
+ }
+
+ @Override
+ public ImmutableMap<?, ?> read(Kryo kryo, Input input,
+ Class<ImmutableMap<?, ?>> type) {
+ Map<?, ?> map = kryo.readObject(input, HashMap.class, mapSerializer);
+ return ImmutableMap.copyOf(map);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableSetSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableSetSerializer.java
new file mode 100644
index 00000000..cb9b0543
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ImmutableSetSerializer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.store.serializers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.serializers.CollectionSerializer;
+import com.google.common.collect.ImmutableSet;
+
+/**
+* Kryo Serializer for {@link ImmutableSet}.
+*/
+public class ImmutableSetSerializer extends Serializer<ImmutableSet<?>> {
+
+ private final CollectionSerializer serializer = new CollectionSerializer();
+
+ /**
+ * Creates {@link ImmutableSet} serializer instance.
+ */
+ public ImmutableSetSerializer() {
+ // non-null, immutable
+ super(false, true);
+ serializer.setElementsCanBeNull(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, ImmutableSet<?> object) {
+ kryo.writeObject(output, object.asList(), serializer);
+ }
+
+ @Override
+ public ImmutableSet<?> read(Kryo kryo, Input input,
+ Class<ImmutableSet<?>> type) {
+ List<?> elms = kryo.readObject(input, ArrayList.class, serializer);
+ return ImmutableSet.copyOf(elms);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4AddressSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4AddressSerializer.java
new file mode 100644
index 00000000..2370ad92
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4AddressSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.store.serializers;
+
+import org.onlab.packet.Ip4Address;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip4Address}.
+ */
+public class Ip4AddressSerializer extends Serializer<Ip4Address> {
+
+ /**
+ * Creates {@link Ip4Address} serializer instance.
+ */
+ public Ip4AddressSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, Ip4Address object) {
+ byte[] octs = object.toOctets();
+ // It is always Ip4Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ }
+
+ @Override
+ public Ip4Address read(Kryo kryo, Input input, Class<Ip4Address> type) {
+ final int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ return Ip4Address.valueOf(octs);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4PrefixSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4PrefixSerializer.java
new file mode 100644
index 00000000..029ffb1a
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip4PrefixSerializer.java
@@ -0,0 +1,57 @@
+/*
+ * 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.store.serializers;
+
+import org.onlab.packet.Ip4Prefix;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip4Prefix}.
+ */
+public final class Ip4PrefixSerializer extends Serializer<Ip4Prefix> {
+
+ /**
+ * Creates {@link Ip4Prefix} serializer instance.
+ */
+ public Ip4PrefixSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output,
+ Ip4Prefix object) {
+ byte[] octs = object.address().toOctets();
+ // It is always Ip4Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ output.writeInt(object.prefixLength());
+ }
+
+ @Override
+ public Ip4Prefix read(Kryo kryo, Input input,
+ Class<Ip4Prefix> type) {
+ int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ int prefLen = input.readInt();
+ return Ip4Prefix.valueOf(octs, prefLen);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6AddressSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6AddressSerializer.java
new file mode 100644
index 00000000..f8101f98
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6AddressSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.store.serializers;
+
+import org.onlab.packet.Ip6Address;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip6Address}.
+ */
+public class Ip6AddressSerializer extends Serializer<Ip6Address> {
+
+ /**
+ * Creates {@link Ip6Address} serializer instance.
+ */
+ public Ip6AddressSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, Ip6Address object) {
+ byte[] octs = object.toOctets();
+ // It is always Ip6Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ }
+
+ @Override
+ public Ip6Address read(Kryo kryo, Input input, Class<Ip6Address> type) {
+ final int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ return Ip6Address.valueOf(octs);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6PrefixSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6PrefixSerializer.java
new file mode 100644
index 00000000..91ba5a29
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/Ip6PrefixSerializer.java
@@ -0,0 +1,57 @@
+/*
+ * 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.store.serializers;
+
+import org.onlab.packet.Ip6Prefix;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip6Prefix}.
+ */
+public final class Ip6PrefixSerializer extends Serializer<Ip6Prefix> {
+
+ /**
+ * Creates {@link Ip6Prefix} serializer instance.
+ */
+ public Ip6PrefixSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output,
+ Ip6Prefix object) {
+ byte[] octs = object.address().toOctets();
+ // It is always Ip6Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ output.writeInt(object.prefixLength());
+ }
+
+ @Override
+ public Ip6Prefix read(Kryo kryo, Input input,
+ Class<Ip6Prefix> type) {
+ int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ int prefLen = input.readInt();
+ return Ip6Prefix.valueOf(octs, prefLen);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpAddressSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpAddressSerializer.java
new file mode 100644
index 00000000..cb85fec8
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpAddressSerializer.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.store.serializers;
+
+import org.onlab.packet.IpAddress;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link IpAddress}.
+ */
+public class IpAddressSerializer extends Serializer<IpAddress> {
+
+ /**
+ * Creates {@link IpAddress} serializer instance.
+ */
+ public IpAddressSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, IpAddress object) {
+ byte[] octs = object.toOctets();
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ }
+
+ @Override
+ public IpAddress read(Kryo kryo, Input input, Class<IpAddress> type) {
+ final int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ // Use the address size to decide whether it is IPv4 or IPv6 address
+ if (octLen == IpAddress.INET_BYTE_LENGTH) {
+ return IpAddress.valueOf(IpAddress.Version.INET, octs);
+ }
+ if (octLen == IpAddress.INET6_BYTE_LENGTH) {
+ return IpAddress.valueOf(IpAddress.Version.INET6, octs);
+ }
+ return null; // Shouldn't be reached
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpPrefixSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpPrefixSerializer.java
new file mode 100644
index 00000000..cf0a20b9
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/IpPrefixSerializer.java
@@ -0,0 +1,64 @@
+/*
+ * 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.store.serializers;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link IpPrefix}.
+ */
+public final class IpPrefixSerializer extends Serializer<IpPrefix> {
+
+ /**
+ * Creates {@link IpPrefix} serializer instance.
+ */
+ public IpPrefixSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output,
+ IpPrefix object) {
+ byte[] octs = object.address().toOctets();
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ output.writeInt(object.prefixLength());
+ }
+
+ @Override
+ public IpPrefix read(Kryo kryo, Input input,
+ Class<IpPrefix> type) {
+ int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ int prefLen = input.readInt();
+ // Use the address size to decide whether it is IPv4 or IPv6 address
+ if (octLen == IpAddress.INET_BYTE_LENGTH) {
+ return IpPrefix.valueOf(IpAddress.Version.INET, octs, prefLen);
+ }
+ if (octLen == IpAddress.INET6_BYTE_LENGTH) {
+ return IpPrefix.valueOf(IpAddress.Version.INET6, octs, prefLen);
+ }
+ return null; // Shouldn't be reached
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
new file mode 100644
index 00000000..66ee7be7
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -0,0 +1,479 @@
+/*
+ * 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.store.serializers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Bandwidth;
+import org.onlab.util.Frequency;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.Version;
+import org.onosproject.incubator.net.domain.IntentDomainId;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultEdgeLink;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Element;
+import org.onosproject.net.GridType;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.IndexedLambda;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.OchPort;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.OduCltPort;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.OmsPort;
+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.DefaultPortStatistics;
+import org.onosproject.net.device.OchPortDescription;
+import org.onosproject.net.device.OduCltPortDescription;
+import org.onosproject.net.device.OmsPortDescription;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+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.FlowRuleExtPayLoad;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.IPDscpCriterion;
+import org.onosproject.net.flow.criteria.IPEcnCriterion;
+import org.onosproject.net.flow.criteria.IPProtocolCriterion;
+import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion;
+import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion;
+import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion;
+import org.onosproject.net.flow.criteria.IcmpCodeCriterion;
+import org.onosproject.net.flow.criteria.IcmpTypeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion;
+import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion;
+import org.onosproject.net.flow.criteria.IndexedLambdaCriterion;
+import org.onosproject.net.flow.criteria.LambdaCriterion;
+import org.onosproject.net.flow.criteria.MetadataCriterion;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.OpticalSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.SctpPortCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.TunnelIdCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.criteria.VlanPcpCriterion;
+import org.onosproject.net.flow.instructions.Instructions;
+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.flow.instructions.L4ModificationInstruction;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.intent.IntentOperation;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.MplsIntent;
+import org.onosproject.net.intent.MplsPathIntent;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.OpticalCircuitIntent;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+import org.onosproject.net.intent.OpticalPathIntent;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.BooleanConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.LinkTypeConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.newresource.ResourceAllocation;
+import org.onosproject.net.newresource.ResourcePath;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.DefaultPacketRequest;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.BandwidthResource;
+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.DefaultLinkResourceRequest;
+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.LinkResourceRequest;
+import org.onosproject.net.resource.link.MplsLabel;
+import org.onosproject.net.resource.link.MplsLabelResourceAllocation;
+import org.onosproject.net.resource.link.MplsLabelResourceRequest;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.SetEvent;
+import org.onosproject.store.service.Versioned;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public final class KryoNamespaces {
+
+ public static final KryoNamespace BASIC = KryoNamespace.newBuilder()
+ .nextId(KryoNamespace.FLOATING_ID)
+ .register(byte[].class)
+ .register(AtomicBoolean.class)
+ .register(AtomicInteger.class)
+ .register(AtomicLong.class)
+ .register(new ImmutableListSerializer(),
+ ImmutableList.class,
+ ImmutableList.of(1).getClass(),
+ ImmutableList.of(1, 2).getClass())
+ .register(new ImmutableSetSerializer(),
+ ImmutableSet.class,
+ ImmutableSet.of().getClass(),
+ ImmutableSet.of(1).getClass(),
+ ImmutableSet.of(1, 2).getClass())
+ .register(new ImmutableMapSerializer(),
+ ImmutableMap.class,
+ ImmutableMap.of().getClass(),
+ ImmutableMap.of("a", 1).getClass(),
+ ImmutableMap.of("R", 2, "D", 2).getClass())
+ .register(HashMap.class)
+ .register(ConcurrentHashMap.class)
+ .register(CopyOnWriteArraySet.class)
+ .register(ArrayList.class,
+ LinkedList.class,
+ HashSet.class
+ )
+ .register(Maps.immutableEntry("a", "b").getClass())
+ .register(new ArraysAsListSerializer(), Arrays.asList().getClass())
+ .register(Collections.singletonList(1).getClass())
+ .register(Duration.class)
+ .register(Collections.emptySet().getClass())
+ .register(Optional.class)
+ .register(Collections.emptyList().getClass())
+ .register(Collections.unmodifiableSet(Collections.emptySet()).getClass())
+ .register(Collections.singleton(Object.class).getClass())
+ .build();
+
+ /**
+ * KryoNamespace which can serialize ON.lab misc classes.
+ */
+ public static final KryoNamespace MISC = KryoNamespace.newBuilder()
+ .nextId(KryoNamespace.FLOATING_ID)
+ .register(new IpPrefixSerializer(), IpPrefix.class)
+ .register(new Ip4PrefixSerializer(), Ip4Prefix.class)
+ .register(new Ip6PrefixSerializer(), Ip6Prefix.class)
+ .register(new IpAddressSerializer(), IpAddress.class)
+ .register(new Ip4AddressSerializer(), Ip4Address.class)
+ .register(new Ip6AddressSerializer(), Ip6Address.class)
+ .register(new MacAddressSerializer(), MacAddress.class)
+ .register(VlanId.class)
+ .register(Frequency.class)
+ .register(Bandwidth.class)
+ .build();
+
+ /**
+ * Kryo registration Id for user custom registration.
+ */
+ public static final int BEGIN_USER_CUSTOM_ID = 300;
+
+ // TODO: Populate other classes
+ /**
+ * KryoNamespace which can serialize API bundle classes.
+ */
+ public static final KryoNamespace API = KryoNamespace.newBuilder()
+ .nextId(KryoNamespace.INITIAL_ID)
+ .register(BASIC)
+ .nextId(KryoNamespace.INITIAL_ID + 30)
+ .register(MISC)
+ .nextId(KryoNamespace.INITIAL_ID + 30 + 10)
+ .register(
+ Instructions.MeterInstruction.class,
+ MeterId.class,
+ Version.class,
+ ControllerNode.State.class,
+ ApplicationState.class,
+ ApplicationRole.class,
+ DefaultApplication.class,
+ Device.Type.class,
+ Port.Type.class,
+ ChassisId.class,
+ DefaultControllerNode.class,
+ DefaultDevice.class,
+ DefaultDeviceDescription.class,
+ DefaultHost.class,
+ DefaultLinkDescription.class,
+ Port.class,
+ DefaultPortDescription.class,
+ Element.class,
+ Link.Type.class,
+ Link.State.class,
+ Timestamp.class,
+ Leadership.class,
+ LeadershipEvent.class,
+ LeadershipEvent.Type.class,
+ HostId.class,
+ HostDescription.class,
+ DefaultHostDescription.class,
+ DefaultFlowEntry.class,
+ StoredFlowEntry.class,
+ FlowRule.Type.class,
+ DefaultFlowRule.class,
+ DefaultFlowEntry.class,
+ DefaultPacketRequest.class,
+ PacketPriority.class,
+ FlowEntry.FlowEntryState.class,
+ FlowId.class,
+ DefaultTrafficSelector.class,
+ PortCriterion.class,
+ MetadataCriterion.class,
+ EthCriterion.class,
+ EthType.class,
+ EthTypeCriterion.class,
+ VlanIdCriterion.class,
+ VlanPcpCriterion.class,
+ IPDscpCriterion.class,
+ IPEcnCriterion.class,
+ IPProtocolCriterion.class,
+ IPCriterion.class,
+ TpPort.class,
+ TcpPortCriterion.class,
+ UdpPortCriterion.class,
+ SctpPortCriterion.class,
+ IcmpTypeCriterion.class,
+ IcmpCodeCriterion.class,
+ IPv6FlowLabelCriterion.class,
+ Icmpv6TypeCriterion.class,
+ Icmpv6CodeCriterion.class,
+ IPv6NDTargetAddressCriterion.class,
+ IPv6NDLinkLayerAddressCriterion.class,
+ MplsCriterion.class,
+ TunnelIdCriterion.class,
+ IPv6ExthdrFlagsCriterion.class,
+ LambdaCriterion.class,
+ IndexedLambdaCriterion.class,
+ OchSignalCriterion.class,
+ OchSignalTypeCriterion.class,
+ OpticalSignalTypeCriterion.class,
+ Criterion.class,
+ Criterion.Type.class,
+ DefaultTrafficTreatment.class,
+ Instructions.DropInstruction.class,
+ Instructions.OutputInstruction.class,
+ Instructions.GroupInstruction.class,
+ Instructions.TableTypeTransition.class,
+ L0ModificationInstruction.class,
+ L0ModificationInstruction.L0SubType.class,
+ L0ModificationInstruction.ModLambdaInstruction.class,
+ L0ModificationInstruction.ModOchSignalInstruction.class,
+ L2ModificationInstruction.class,
+ L2ModificationInstruction.L2SubType.class,
+ L2ModificationInstruction.ModEtherInstruction.class,
+ L2ModificationInstruction.PushHeaderInstructions.class,
+ L2ModificationInstruction.ModVlanIdInstruction.class,
+ L2ModificationInstruction.ModVlanPcpInstruction.class,
+ L2ModificationInstruction.PopVlanInstruction.class,
+ L2ModificationInstruction.ModMplsLabelInstruction.class,
+ L2ModificationInstruction.ModMplsTtlInstruction.class,
+ L2ModificationInstruction.ModTunnelIdInstruction.class,
+ L3ModificationInstruction.class,
+ L3ModificationInstruction.L3SubType.class,
+ L3ModificationInstruction.ModIPInstruction.class,
+ L3ModificationInstruction.ModIPv6FlowLabelInstruction.class,
+ L3ModificationInstruction.ModTtlInstruction.class,
+ L4ModificationInstruction.class,
+ L4ModificationInstruction.L4SubType.class,
+ L4ModificationInstruction.ModTransportPortInstruction.class,
+ RoleInfo.class,
+ FlowRuleBatchEvent.class,
+ FlowRuleBatchEvent.Type.class,
+ FlowRuleBatchRequest.class,
+ FlowRuleBatchOperation.class,
+ FlowRuleEvent.class,
+ FlowRuleEvent.Type.class,
+ CompletedBatchOperation.class,
+ FlowRuleBatchEntry.class,
+ FlowRuleBatchEntry.FlowRuleOperation.class,
+ IntentId.class,
+ IntentState.class,
+ //Key.class, is abstract
+ Key.of(1L, new DefaultApplicationId(0, "bar")).getClass(), //LongKey.class
+ Key.of("foo", new DefaultApplicationId(0, "bar")).getClass(), //StringKey.class
+ Intent.class,
+ ConnectivityIntent.class,
+ PathIntent.class,
+ DefaultPath.class,
+ DefaultEdgeLink.class,
+ HostToHostIntent.class,
+ PointToPointIntent.class,
+ MultiPointToSinglePointIntent.class,
+ SinglePointToMultiPointIntent.class,
+ FlowRuleIntent.class,
+ LinkCollectionIntent.class,
+ OpticalConnectivityIntent.class,
+ OpticalPathIntent.class,
+ OpticalCircuitIntent.class,
+ LinkResourceRequest.class,
+ DefaultLinkResourceRequest.class,
+ BandwidthResourceRequest.class,
+ LambdaResourceRequest.class,
+ LambdaResource.class,
+ BandwidthResource.class,
+ DefaultLinkResourceAllocations.class,
+ BandwidthResourceAllocation.class,
+ LambdaResourceAllocation.class,
+ ResourcePath.class,
+ ResourceAllocation.class,
+ // Constraints
+ LambdaConstraint.class,
+ BandwidthConstraint.class,
+ LinkTypeConstraint.class,
+ LatencyConstraint.class,
+ WaypointConstraint.class,
+ ObstacleConstraint.class,
+ AnnotationConstraint.class,
+ BooleanConstraint.class,
+ PartialFailureConstraint.class,
+ IntentOperation.class,
+ FlowRuleExtPayLoad.class,
+ Frequency.class,
+ DefaultAnnotations.class,
+ PortStatistics.class,
+ DefaultPortStatistics.class,
+ IntentDomainId.class
+ )
+ .register(new DefaultApplicationIdSerializer(), DefaultApplicationId.class)
+ .register(new URISerializer(), URI.class)
+ .register(new NodeIdSerializer(), NodeId.class)
+ .register(new ProviderIdSerializer(), ProviderId.class)
+ .register(new DeviceIdSerializer(), DeviceId.class)
+ .register(new PortNumberSerializer(), PortNumber.class)
+ .register(new DefaultPortSerializer(), DefaultPort.class)
+ .register(new LinkKeySerializer(), LinkKey.class)
+ .register(new ConnectPointSerializer(), ConnectPoint.class)
+ .register(new DefaultLinkSerializer(), DefaultLink.class)
+ .register(new MastershipTermSerializer(), MastershipTerm.class)
+ .register(new HostLocationSerializer(), HostLocation.class)
+ .register(new DefaultOutboundPacketSerializer(), DefaultOutboundPacket.class)
+ .register(new AnnotationsSerializer(), DefaultAnnotations.class)
+ .register(Versioned.class)
+ .register(MapEvent.class)
+ .register(MapEvent.Type.class)
+ .register(SetEvent.class)
+ .register(SetEvent.Type.class)
+ .register(DefaultGroupId.class)
+ .register(Annotations.class)
+ .register(OmsPort.class)
+ .register(OchPort.class)
+ .register(OduSignalType.class)
+ .register(OchSignalType.class)
+ .register(GridType.class)
+ .register(ChannelSpacing.class)
+ .register(OduCltPort.class)
+ .register(OduCltPort.SignalType.class)
+ .register(IndexedLambda.class)
+ .register(OchSignal.class)
+ .register(OduCltPortDescription.class)
+ .register(OchPortDescription.class)
+ .register(OmsPortDescription.class)
+ .register(
+ MplsIntent.class,
+ MplsPathIntent.class,
+ MplsLabelResourceAllocation.class,
+ MplsLabelResourceRequest.class,
+ MplsLabel.class,
+ org.onlab.packet.MplsLabel.class,
+ org.onlab.packet.MPLS.class
+ )
+
+ .build();
+
+
+ // not to be instantiated
+ private KryoNamespaces() {}
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoSerializer.java
new file mode 100644
index 00000000..dbad6d0d
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoSerializer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.store.serializers;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import org.onlab.util.KryoNamespace;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * StoreSerializer implementation using Kryo.
+ */
+public class KryoSerializer implements StoreSerializer {
+
+ protected KryoNamespace serializerPool;
+
+ public KryoSerializer() {
+ setupKryoPool();
+ }
+
+ /**
+ * Sets up the common serializers pool.
+ */
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .build();
+ }
+
+ @Override
+ public byte[] encode(final Object obj) {
+ return serializerPool.serialize(obj);
+ }
+
+ @Override
+ public <T> T decode(final byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ return serializerPool.deserialize(bytes);
+ }
+
+ @Override
+ public void encode(Object obj, ByteBuffer buffer) {
+ serializerPool.serialize(obj, buffer);
+ }
+
+ @Override
+ public <T> T decode(ByteBuffer buffer) {
+ return serializerPool.deserialize(buffer);
+ }
+
+ @Override
+ public void encode(Object obj, OutputStream stream) {
+ serializerPool.serialize(obj, stream);
+ }
+
+ @Override
+ public <T> T decode(InputStream stream) {
+ return serializerPool.deserialize(stream);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("serializerPool", serializerPool)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/LinkKeySerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/LinkKeySerializer.java
new file mode 100644
index 00000000..4bef3369
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/LinkKeySerializer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.store.serializers;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.LinkKey;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link LinkKey}.
+ */
+public class LinkKeySerializer extends Serializer<LinkKey> {
+
+ /**
+ * Creates {@link LinkKey} serializer instance.
+ */
+ public LinkKeySerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, LinkKey object) {
+ kryo.writeClassAndObject(output, object.src());
+ kryo.writeClassAndObject(output, object.dst());
+ }
+
+ @Override
+ public LinkKey read(Kryo kryo, Input input, Class<LinkKey> type) {
+ ConnectPoint src = (ConnectPoint) kryo.readClassAndObject(input);
+ ConnectPoint dst = (ConnectPoint) kryo.readClassAndObject(input);
+ return LinkKey.linkKey(src, dst);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MacAddressSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MacAddressSerializer.java
new file mode 100644
index 00000000..881e0ec0
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MacAddressSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.store.serializers;
+
+import org.onlab.packet.MacAddress;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link MacAddress}.
+ */
+public class MacAddressSerializer extends Serializer<MacAddress> {
+
+ /**
+ * Creates {@link MacAddress} serializer instance.
+ */
+ public MacAddressSerializer() {
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, MacAddress object) {
+ output.writeBytes(object.toBytes());
+ }
+
+ @Override
+ public MacAddress read(Kryo kryo, Input input, Class<MacAddress> type) {
+ return MacAddress.valueOf(input.readBytes(MacAddress.MAC_ADDRESS_LENGTH));
+ }
+
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MastershipTermSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MastershipTermSerializer.java
new file mode 100644
index 00000000..43bce71c
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/MastershipTermSerializer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.store.serializers;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipTerm;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link org.onosproject.mastership.MastershipTerm}.
+ */
+public class MastershipTermSerializer extends Serializer<MastershipTerm> {
+
+ /**
+ * Creates {@link MastershipTerm} serializer instance.
+ */
+ public MastershipTermSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public MastershipTerm read(Kryo kryo, Input input, Class<MastershipTerm> type) {
+ final NodeId node = (NodeId) kryo.readClassAndObject(input);
+ final long term = input.readLong();
+ return MastershipTerm.of(node, term);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, MastershipTerm object) {
+ kryo.writeClassAndObject(output, object.master());
+ output.writeLong(object.termNumber());
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/NodeIdSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/NodeIdSerializer.java
new file mode 100644
index 00000000..da8939b0
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/NodeIdSerializer.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.store.serializers;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Kryo Serializer for {@link org.onosproject.cluster.NodeId}.
+ */
+public final class NodeIdSerializer extends Serializer<NodeId> {
+
+ /**
+ * Creates {@link NodeId} serializer instance.
+ */
+ public NodeIdSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, NodeId object) {
+ output.writeString(object.toString());
+ }
+
+ @Override
+ public NodeId read(Kryo kryo, Input input, Class<NodeId> type) {
+ final String id = input.readString();
+ return new NodeId(id);
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/PortNumberSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/PortNumberSerializer.java
new file mode 100644
index 00000000..74db644d
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/PortNumberSerializer.java
@@ -0,0 +1,56 @@
+/*
+ * 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.store.serializers;
+
+import org.onosproject.net.PortNumber;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Serializer for {@link PortNumber}.
+ */
+public final class PortNumberSerializer extends
+ Serializer<PortNumber> {
+
+ /**
+ * Creates {@link PortNumber} serializer instance.
+ */
+ public PortNumberSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, PortNumber object) {
+ output.writeBoolean(object.hasName());
+ output.writeLong(object.toLong());
+ if (object.hasName()) {
+ output.writeString(object.name());
+ }
+ }
+
+ @Override
+ public PortNumber read(Kryo kryo, Input input, Class<PortNumber> type) {
+ if (input.readBoolean()) {
+ return PortNumber.portNumber(input.readLong(), input.readString());
+ } else {
+ return PortNumber.portNumber(input.readLong());
+ }
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ProviderIdSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ProviderIdSerializer.java
new file mode 100644
index 00000000..7f9b6ffc
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ProviderIdSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.store.serializers;
+
+import org.onosproject.net.provider.ProviderId;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Serializer for {@link ProviderId}.
+ */
+public class ProviderIdSerializer extends Serializer<ProviderId> {
+
+ /**
+ * Creates {@link ProviderId} serializer instance.
+ */
+ public ProviderIdSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, ProviderId object) {
+ output.writeString(object.scheme());
+ output.writeString(object.id());
+ output.writeBoolean(object.isAncillary());
+ }
+
+ @Override
+ public ProviderId read(Kryo kryo, Input input, Class<ProviderId> type) {
+ String scheme = input.readString();
+ String id = input.readString();
+ boolean isAncillary = input.readBoolean();
+ return new ProviderId(scheme, id, isAncillary);
+ }
+
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/StoreSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/StoreSerializer.java
new file mode 100644
index 00000000..f0149fc5
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/StoreSerializer.java
@@ -0,0 +1,78 @@
+/*
+ * 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.store.serializers;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+// TODO: To be replaced with SerializationService from IOLoop activity
+/**
+ * Service to serialize Objects into byte array.
+ */
+public interface StoreSerializer {
+
+ /**
+ * Serializes the specified object into bytes.
+ *
+ * @param obj object to be serialized
+ * @return serialized bytes
+ */
+ byte[] encode(final Object obj);
+
+ /**
+ * Serializes the specified object into bytes.
+ *
+ * @param obj object to be serialized
+ * @param buffer to write serialized bytes
+ */
+ void encode(final Object obj, ByteBuffer buffer);
+
+ /**
+ * Serializes the specified object into bytes.
+ *
+ * @param obj object to be serialized
+ * @param stream to write serialized bytes
+ */
+ void encode(final Object obj, final OutputStream stream);
+
+ /**
+ * Deserializes the specified bytes into an object.
+ *
+ * @param bytes bytes to be deserialized
+ * @return deserialized object
+ * @param <T> decoded type
+ */
+ <T> T decode(final byte[] bytes);
+
+ /**
+ * Deserializes the specified bytes into an object.
+ *
+ * @param buffer bytes to be deserialized
+ * @return deserialized object
+ * @param <T> decoded type
+ */
+ <T> T decode(final ByteBuffer buffer);
+
+ /**
+ * Deserializes the specified bytes into an object.
+ *
+ * @param stream stream containing the bytes to be deserialized
+ * @return deserialized object
+ * @param <T> decoded type
+ */
+ <T> T decode(final InputStream stream);
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/URISerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/URISerializer.java
new file mode 100644
index 00000000..43969666
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/URISerializer.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.store.serializers;
+
+import java.net.URI;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Serializer for {@link URI}.
+ */
+public class URISerializer extends Serializer<URI> {
+
+ /**
+ * Creates {@link URI} serializer instance.
+ */
+ public URISerializer() {
+ super(false);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, URI object) {
+ output.writeString(object.toString());
+ }
+
+ @Override
+ public URI read(Kryo kryo, Input input, Class<URI> type) {
+ return URI.create(input.readString());
+ }
+}
diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/package-info.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/package-info.java
new file mode 100644
index 00000000..9471b43e
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/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.
+ */
+
+/**
+ * Various Kryo serializers for use in distributed stores.
+ */
+package org.onosproject.store.serializers;
diff --git a/framework/src/onos/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java b/framework/src/onos/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
new file mode 100644
index 00000000..97ccb836
--- /dev/null
+++ b/framework/src/onos/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
@@ -0,0 +1,470 @@
+/*
+ * 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.store.serializers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Bandwidth;
+import org.onlab.util.Frequency;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.OchPort;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OduCltPort;
+import org.onosproject.net.OmsPort;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleBatchEntry;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.newresource.ResourcePath;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.resource.link.BandwidthResource;
+import org.onosproject.net.resource.link.BandwidthResourceAllocation;
+import org.onosproject.net.resource.link.DefaultLinkResourceAllocations;
+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.LinkResourceRequest;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.intent.constraint.AnnotationConstraint;
+import org.onosproject.net.intent.constraint.BandwidthConstraint;
+import org.onosproject.net.intent.constraint.LambdaConstraint;
+import org.onosproject.net.intent.constraint.LatencyConstraint;
+import org.onosproject.net.intent.constraint.LinkTypeConstraint;
+import org.onosproject.net.intent.constraint.ObstacleConstraint;
+import org.onosproject.net.intent.constraint.WaypointConstraint;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.KryoNamespace;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.time.Duration;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.*;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+public class KryoSerializerTest {
+
+ private static final ProviderId PID = new ProviderId("of", "foo");
+ private static final ProviderId PIDA = new ProviderId("of", "foo", true);
+ 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 PortNumber P2 = portNumber(2);
+ private static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+ private static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+ 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 Device DEV1 = new DefaultDevice(PID, DID1, Device.Type.SWITCH, MFR, HW,
+ SW1, SN, CID);
+ private static final SparseAnnotations A1 = DefaultAnnotations.builder()
+ .set("A1", "a1")
+ .set("B1", "b1")
+ .build();
+ private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+ .remove("A1")
+ .set("B3", "b3")
+ .build();
+ private static final OchSignal OCH_SIGNAL1 = (OchSignal) org.onosproject.net.Lambda.ochSignal(
+ GridType.DWDM, ChannelSpacing.CHL_100GHZ, -8, 4);
+ private static final VlanId VLAN1 = VlanId.vlanId((short) 100);
+
+ private KryoSerializer serializer;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ serializer = new KryoSerializer() {
+
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .build();
+ }
+ };
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ private <T> void testSerializedEquals(T original) {
+ ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+ serializer.encode(original, buffer);
+ buffer.flip();
+ T copy = serializer.decode(buffer);
+
+ T copy2 = serializer.decode(serializer.encode(original));
+
+ new EqualsTester()
+ .addEqualityGroup(original, copy, copy2)
+ .testEquals();
+ }
+
+ private <T> void testSerializable(T original) {
+ byte[] bs = serializer.encode(original);
+ T copy = serializer.decode(bs);
+ assertNotNull(copy);
+ }
+
+
+ @Test
+ public void testConnectPoint() {
+ testSerializedEquals(new ConnectPoint(DID1, P1));
+ }
+
+ @Test
+ public void testDefaultLink() {
+ testSerializedEquals(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT));
+ testSerializedEquals(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT, A1));
+ }
+
+ @Test
+ public void testDefaultPort() {
+ testSerializedEquals(new DefaultPort(DEV1, P1, true));
+ testSerializedEquals(new DefaultPort(DEV1, P1, true, A1_2));
+ }
+
+ @Test
+ public void testOmsPort() {
+ testSerializedEquals(new OmsPort(DEV1, P1, true, Frequency.ofGHz(190_100), Frequency.ofGHz(197_300),
+ Frequency.ofGHz(100)));
+ testSerializedEquals(new OmsPort(DEV1, P1, true, Frequency.ofGHz(190_100), Frequency.ofGHz(197_300),
+ Frequency.ofGHz(100), A1_2));
+ }
+
+ @Test
+ public void testOchPort() {
+ testSerializedEquals(new OchPort(DEV1, P1, true, OduSignalType.ODU0, false, OCH_SIGNAL1));
+ testSerializedEquals(new OchPort(DEV1, P1, true, OduSignalType.ODU0, false, OCH_SIGNAL1, A1_2));
+ }
+
+ @Test
+ public void testOduCltPort() {
+ testSerializedEquals(new OduCltPort(DEV1, P1, true, OduCltPort.SignalType.CLT_10GBE));
+ testSerializedEquals(new OduCltPort(DEV1, P1, true, OduCltPort.SignalType.CLT_10GBE, A1_2));
+ }
+
+ @Test
+ public void testDeviceId() {
+ testSerializedEquals(DID1);
+ }
+
+ @Test
+ public void testImmutableMap() {
+ testSerializedEquals(ImmutableMap.of(DID1, DEV1, DID2, DEV1));
+ testSerializedEquals(ImmutableMap.of(DID1, DEV1));
+ testSerializedEquals(ImmutableMap.of());
+ }
+
+ @Test
+ public void testImmutableSet() {
+ testSerializedEquals(ImmutableSet.of(DID1, DID2));
+ testSerializedEquals(ImmutableSet.of(DID1));
+ testSerializedEquals(ImmutableSet.of());
+ }
+
+ @Test
+ public void testImmutableList() {
+ testSerializedEquals(ImmutableList.of(DID1, DID2));
+ testSerializedEquals(ImmutableList.of(DID1));
+ testSerializedEquals(ImmutableList.of());
+ }
+
+ @Test
+ public void testFlowRuleBatchEntry() {
+ final FlowRule rule1 =
+ DefaultFlowRule.builder()
+ .forDevice(DID1)
+ .withSelector(DefaultTrafficSelector.emptySelector())
+ .withTreatment(DefaultTrafficTreatment.emptyTreatment())
+ .withPriority(0)
+ .fromApp(new DefaultApplicationId(1, "1"))
+ .makeTemporary(1)
+ .build();
+
+ final FlowRuleBatchEntry entry1 =
+ new FlowRuleBatchEntry(FlowRuleBatchEntry.FlowRuleOperation.ADD, rule1);
+ final FlowRuleBatchEntry entry2 =
+ new FlowRuleBatchEntry(FlowRuleBatchEntry.FlowRuleOperation.ADD, rule1, 100L);
+
+ testSerializedEquals(entry1);
+ testSerializedEquals(entry2);
+ }
+
+ @Test
+ public void testIpPrefix() {
+ testSerializedEquals(IpPrefix.valueOf("192.168.0.1/24"));
+ }
+
+ @Test
+ public void testIp4Prefix() {
+ testSerializedEquals(Ip4Prefix.valueOf("192.168.0.1/24"));
+ }
+
+ @Test
+ public void testIp6Prefix() {
+ testSerializedEquals(Ip6Prefix.valueOf("1111:2222::/120"));
+ }
+
+ @Test
+ public void testIpAddress() {
+ testSerializedEquals(IpAddress.valueOf("192.168.0.1"));
+ }
+
+ @Test
+ public void testIp4Address() {
+ testSerializedEquals(Ip4Address.valueOf("192.168.0.1"));
+ }
+
+ @Test
+ public void testIp6Address() {
+ testSerializedEquals(Ip6Address.valueOf("1111:2222::"));
+ }
+
+ @Test
+ public void testMacAddress() {
+ testSerializedEquals(MacAddress.valueOf("12:34:56:78:90:ab"));
+ }
+
+ @Test
+ public void testLinkKey() {
+ testSerializedEquals(LinkKey.linkKey(CP1, CP2));
+ }
+
+ @Test
+ public void testNodeId() {
+ testSerializedEquals(new NodeId("SomeNodeIdentifier"));
+ }
+
+ @Test
+ public void testPortNumber() {
+ testSerializedEquals(P1);
+ }
+
+ @Test
+ public void testProviderId() {
+ testSerializedEquals(PID);
+ testSerializedEquals(PIDA);
+ }
+
+ @Test
+ public void testMastershipTerm() {
+ testSerializedEquals(MastershipTerm.of(new NodeId("foo"), 2));
+ testSerializedEquals(MastershipTerm.of(null, 0));
+ }
+
+ @Test
+ public void testHostLocation() {
+ testSerializedEquals(new HostLocation(CP1, 1234L));
+ }
+
+ @Test
+ public void testFlowId() {
+ testSerializedEquals(FlowId.valueOf(0x12345678L));
+ }
+
+ @Test
+ public void testRoleInfo() {
+ testSerializedEquals(new RoleInfo(new NodeId("master"),
+ asList(new NodeId("stby1"), new NodeId("stby2"))));
+ }
+
+ @Test
+ public void testIndexedLambda() {
+ testSerializedEquals(org.onosproject.net.Lambda.indexedLambda(10L));
+ }
+
+ @Test
+ public void testOchSignal() {
+ testSerializedEquals(org.onosproject.net.Lambda.ochSignal(
+ GridType.DWDM, ChannelSpacing.CHL_100GHZ, 1, 1
+ ));
+ }
+
+ @Test
+ public void testDefaultLinkResourceRequest() {
+ testSerializable(DefaultLinkResourceRequest.builder(IntentId.valueOf(2501), ImmutableList.of())
+ .addLambdaRequest()
+ .addBandwidthRequest(32.195)
+ .build()
+ );
+ }
+
+ @Test
+ public void testDefaultLinkResourceAllocations() {
+ LinkResourceRequest request = DefaultLinkResourceRequest
+ .builder(IntentId.valueOf(2501), ImmutableList.of())
+ .addLambdaRequest()
+ .addBandwidthRequest(32.195)
+ .build();
+ Map<Link, Set<ResourceAllocation>> allocations = new HashMap<>();
+ allocations.put(new DefaultLink(PID, CP1, CP2, Type.DIRECT),
+ ImmutableSet.of(new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(10.0))),
+ new LambdaResourceAllocation(LambdaResource.valueOf(1))));
+ testSerializable(new DefaultLinkResourceAllocations(request, allocations));
+ }
+
+ @Test
+ public void testResourcePath() {
+ testSerializedEquals(new ResourcePath(LinkKey.linkKey(CP1, CP2), VLAN1));
+ }
+
+ @Test
+ public void testResourceAllocation() {
+ testSerializedEquals(new org.onosproject.net.newresource.ResourceAllocation(
+ new ResourcePath(LinkKey.linkKey(CP1, CP2), VLAN1),
+ IntentId.valueOf(30)));
+ }
+
+ @Test
+ public void testFrequency() {
+ testSerializedEquals(Frequency.ofGHz(100));
+ }
+
+ @Test
+ public void testBandwidth() {
+ testSerializedEquals(Bandwidth.mbps(1000.0));
+ }
+
+ @Test
+ public void testLambdaConstraint() {
+ testSerializable(new LambdaConstraint(LambdaResource.valueOf(1)));
+ }
+
+ @Test
+ public void testBandwidthConstraint() {
+ testSerializable(new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(1000.0))));
+ }
+
+ @Test
+ public void testLinkTypeConstraint() {
+ testSerializable(new LinkTypeConstraint(true, Link.Type.DIRECT));
+ }
+
+ @Test
+ public void testLatencyConstraint() {
+ testSerializable(new LatencyConstraint(Duration.ofSeconds(10)));
+ }
+
+ @Test
+ public void testWaypointConstraint() {
+ testSerializable(new WaypointConstraint(deviceId("of:1"), deviceId("of:2")));
+ }
+
+ @Test
+ public void testObstacleConstraint() {
+ testSerializable(new ObstacleConstraint(deviceId("of:1"), deviceId("of:2")));
+ }
+
+ @Test
+ public void testArraysAsList() {
+ testSerializedEquals(Arrays.asList(1, 2, 3));
+ }
+
+ @Test
+ public void testAnnotationConstraint() {
+ testSerializable(new AnnotationConstraint("distance", 100.0));
+ }
+
+ @Test
+ public void testDefaultGroupId() {
+ testSerializedEquals(new DefaultGroupId(99));
+ }
+
+ @Test
+ public void testEmptySet() {
+ testSerializedEquals(Collections.emptySet());
+ }
+
+ @Test
+ public void testAnnotations() {
+ // Annotations does not have equals defined, manually test equality
+ final byte[] a1Bytes = serializer.encode(A1);
+ SparseAnnotations copiedA1 = serializer.decode(a1Bytes);
+ assertAnnotationsEquals(copiedA1, A1);
+
+ final byte[] a12Bytes = serializer.encode(A1_2);
+ SparseAnnotations copiedA12 = serializer.decode(a12Bytes);
+ assertAnnotationsEquals(copiedA12, A1_2);
+ }
+
+ // code clone
+ protected static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+ SparseAnnotations expected = DefaultAnnotations.builder().build();
+ for (SparseAnnotations a : annotations) {
+ expected = DefaultAnnotations.union(expected, a);
+ }
+ assertEquals(expected.keys(), actual.keys());
+ for (String key : expected.keys()) {
+ assertEquals(expected.value(key), actual.value(key));
+ }
+ }
+
+}