--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>commons.opendaylight</artifactId>
+ <version>1.4.2-SNAPSHOT</version>
+ <relativePath>../../opendaylight/commons/opendaylight</relativePath>
+ </parent>
+ <artifactId>extras-features</artifactId>
+ <packaging>kar</packaging>
+ <name>${project.artifactId}</name>
+ <description>Base Features POM</description>
+ <properties>
+ <features.file>features.xml</features.file>
+ <netty3.version>3.9.2.Final</netty3.version>
+ </properties>
+ <build>
+ <resources>
+ <resource>
+ <filtering>true</filtering>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <version>${karaf.version}</version>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>features-create-kar</id>
+ <goals>
+ <goal>features-create-kar</goal>
+ </goals>
+ <configuration>
+ <featuresFile>${project.build.directory}/classes/${features.file}</featuresFile>
+ </configuration>
+ </execution>
+ </executions>
+ <!-- There is no useful configuration for the kar mojo. The features-generate-descriptor mojo configuration may be useful -->
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>filter</id>
+ <goals>
+ <goal>resources</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/${features.file}</file>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="extras-features-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
+
+ <feature name="osgi-compendium" description="OSGi compendium feature" version="${osgi.version}" resolver="(obr)">
+ <bundle start-level="10">mvn:org.osgi/org.osgi.compendium/${osgi.compendium.version}</bundle>
+ </feature>
+
+ <feature name="odl-extras-scala" description="Scala Runtime for OpenDaylight" version="${scala.version}" resolver="(obr)" start-level="10">
+ <bundle>mvn:org.scala-lang/scala-library/${scala.version}.${scala.micro.version}</bundle>
+ <bundle>mvn:org.scala-lang/scala-reflect/${scala.version}.${scala.micro.version}</bundle>
+ </feature>
+
+ <feature name="odl-extras-akka-system" description="Akka Actor Framework System Bundles" version="${akka.version}" resolver="(obr)" start-level="15">
+ <feature version="${scala.version}">odl-extras-scala</feature>
+ <bundle dependency="true">mvn:com.typesafe/config/${typesafe.config.version}</bundle>
+ <bundle dependency="true">mvn:com.typesafe.akka/akka-actor_${scala.version}/${akka.version}</bundle>
+ <bundle dependency="true">mvn:com.typesafe.akka/akka-slf4j_${scala.version}/${akka.version}</bundle>
+ <bundle>mvn:com.typesafe.akka/akka-osgi_${scala.version}/${akka.version}</bundle>
+ </feature>
+
+ <feature name="odl-extras-akka-clustering" description="Akka Clustering Support" version="${akka.version}" resolver="(obr)" start-level="20">
+ <feature version="${akka.version}">odl-extras-akka-system</feature>
+ <bundle dependency="true">wrap:mvn:org.uncommons.maths/uncommons-maths/${uncommons.maths.version}</bundle>
+ <bundle dependency="true">mvn:com.google.protobuf/protobuf-java/${protobuf.version}</bundle>
+ <bundle dependency="true">wrap:mvn:io.netty/netty/${netty3.version}</bundle>
+ <bundle>mvn:com.typesafe.akka/akka-remote_${scala.version}/${akka.version}</bundle>
+ <bundle>mvn:com.typesafe.akka/akka-cluster_${scala.version}/${akka.version}</bundle>
+ </feature>
+
+ <feature name='odl-extras-leveldb' description='LevelDB feature' version='0.7' resolver='(obr)'>
+ <bundle start-level="20">wrap:mvn:org.iq80.leveldb/leveldb/${leveldb.version}</bundle>
+ <bundle start-level="20">wrap:mvn:org.fusesource.leveldbjni/leveldbjni-all/${leveldbjni.version}</bundle>
+ </feature>
+</features>
<properties>
- <akka.version>2.3.2</akka.version>
+ <akka.version>2.3.4</akka.version>
<aopalliance.version>1.0.0</aopalliance.version>
<appauth.version>0.4.2-SNAPSHOT</appauth.version>
<archetype-app-northbound>0.0.1-SNAPSHOT</archetype-app-northbound>
<compiler.version>2.3.2</compiler.version>
<commons.httpclient.version>0.1.2-SNAPSHOT</commons.httpclient.version>
<concepts.version>0.5.2-SNAPSHOT</concepts.version>
+ <concurrentlinkedhashmap.version>1.4</concurrentlinkedhashmap.version>
<config.version>0.2.5-SNAPSHOT</config.version>
<configuration.implementation.version>0.4.3-SNAPSHOT</configuration.implementation.version>
<configuration.version>0.4.3-SNAPSHOT</configuration.version>
<karaf.branding.version>1.0.0-SNAPSHOT</karaf.branding.version>
<karaf.shell.version>3.0.0</karaf.shell.version>
<karaf.version>3.0.1</karaf.version>
+ <leveldb.version>0.7</leveldb.version>
+ <leveldbjni.version>1.8</leveldbjni.version>
<lifecycle.mapping.version>1.0.0</lifecycle.mapping.version>
<logback.version>1.0.9</logback.version>
<logging.bridge.version>0.4.2-SNAPSHOT</logging.bridge.version>
<maven.plugin.api.version>3.0.5</maven.plugin.api.version>
+ <mimepull.version>1.9.4</mimepull.version>
<mdsal.version>1.1-SNAPSHOT</mdsal.version>
<netconf.version>0.2.5-SNAPSHOT</netconf.version>
<networkconfig.bridgedomain.northbound.version>0.0.3-SNAPSHOT</networkconfig.bridgedomain.northbound.version>
<northbound.jolokia.version>1.4.2-SNAPSHOT</northbound.jolokia.version>
<opendaylight-l2-types.version>2013.08.27.4-SNAPSHOT</opendaylight-l2-types.version>
<osgi-brandfragment.web.version>0.0.2-SNAPSHOT</osgi-brandfragment.web.version>
+ <parboiled.version>1.1.6</parboiled.version>
+ <parboiled.scala.version>1.1.6</parboiled.scala.version>
<propertymavenplugin.version>1.0-alpha-2</propertymavenplugin.version>
+ <protobuf.version>2.5.0</protobuf.version>
<protocol-framework.version>0.5.0-SNAPSHOT</protocol-framework.version>
<protocol_plugins.openflow.version>0.4.2-SNAPSHOT</protocol_plugins.openflow.version>
<protocol_plugins.stub.version>0.4.2-SNAPSHOT</protocol_plugins.stub.version>
<samples.loadbalancer.northbound.version>0.4.2-SNAPSHOT</samples.loadbalancer.northbound.version>
<samples.simpleforwarding.version>0.4.2-SNAPSHOT</samples.simpleforwarding.version>
<sanitytest.version>0.4.2-SNAPSHOT</sanitytest.version>
- <scala.version>2.11</scala.version>
+ <scala.version>2.10</scala.version>
+ <scala.micro.version>4</scala.micro.version>
<security.version>0.4.2-SNAPSHOT</security.version>
+ <shapeless.version>1.2.4</shapeless.version>
<sitedeploy>dav:http://nexus.opendaylight.org/content/sites/site</sitedeploy>
<sonar.branch>${user.name}-private-view</sonar.branch>
<sonar.host.url>https://sonar.opendaylight.org/</sonar.host.url>
<spring-security-karaf.version>3.1.4.RELEASE</spring-security-karaf.version>
<spring-security.version>3.1.3.RELEASE</spring-security.version>
<spring.version>3.1.3.RELEASE</spring.version>
+ <sonar.skippedModules>org.openflow.openflowj,net.sf.jung2</sonar.skippedModules>
<statistics.northbound.version>0.4.2-SNAPSHOT</statistics.northbound.version>
<statisticsmanager.implementation.version>0.4.2-SNAPSHOT</statisticsmanager.implementation.version>
<statisticsmanager.version>0.5.1-SNAPSHOT</statisticsmanager.version>
<topology.web.version>0.4.2-SNAPSHOT</topology.web.version>
<topologymanager.version>0.4.2-SNAPSHOT</topologymanager.version>
<troubleshoot.web.version>0.4.2-SNAPSHOT</troubleshoot.web.version>
+ <typesafe.config.version>1.2.0</typesafe.config.version>
+ <uncommons.maths.version>1.2.2</uncommons.maths.version>
<usermanager.implementation.version>0.4.2-SNAPSHOT</usermanager.implementation.version>
<usermanager.northbound.version>0.0.2-SNAPSHOT</usermanager.northbound.version>
<usermanager.version>0.4.2-SNAPSHOT</usermanager.version>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-api</artifactId>
+ <version>${config.version}</version>
</dependency>
</dependencies>
-// vi: set smarttab et sw=4 tabstop=4:
module ${module-name}-${module-implementation-name} {
yang-version 1;
-// vi: set smarttab et sw=4 tabstop=4:
module ${module-name} {
yang-version 1;
<relativePath>..</relativePath>
</parent>
<artifactId>config-util</artifactId>
- <packaging>jar</packaging>
+ <packaging>bundle</packaging>
<name>${project.artifactId}</name>
<dependencies>
<threadCount>1</threadCount>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>org.opendaylight.controller.config.util</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
</plugins>
</build>
<?xml version="1.0" encoding="UTF-8"?>
<features name="config-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
- <feature name='config-all' version='${project.version}'>
- <feature version='${project.version}'>odl-config-subsystem</feature>
- </feature>
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+ <feature name='config-all' version='${project.version}'>
+ <feature version='${project.version}'>odl-config-subsystem</feature>
+ </feature>
- <feature name='odl-config-subsystem' version='${project.version}'>
- <feature version='${yangtools.version}'>yangtools-concepts</feature>
- <feature version='${yangtools.version}'>yangtools-binding</feature>
- <feature version='${yangtools.version}'>yangtools-binding-generator</feature>
- <feature version='${mdsal.version}'>odl-mdsal-commons</feature>
- <bundle>mvn:org.opendaylight.controller/config-api/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/config-manager/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/yang-jmx-generator/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/config-persister-api/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/config-persister-file-xml-adapter/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/config-persister-directory-xml-adapter/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/shutdown-api/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller/shutdown-impl/${project.version}</bundle>
- <bundle>mvn:org.osgi/org.osgi.core/${osgi.core.version}</bundle>
- <bundle>wrap:mvn:com.google.guava/guava/${guava.version}</bundle>
- <bundle>mvn:org.javassist/javassist/${javassist.version}</bundle>
- </feature>
+ <feature name='odl-config-subsystem' version='${project.version}'>
+ <feature version='${yangtools.version}'>yangtools-concepts</feature>
+ <feature version='${yangtools.version}'>yangtools-binding</feature>
+ <feature version='${yangtools.version}'>yangtools-binding-generator</feature>
+ <feature version='${mdsal.version}'>odl-mdsal-commons</feature>
+ <bundle>mvn:org.opendaylight.controller/config-api/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/config-util/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/config-manager/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/yang-jmx-generator/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/config-persister-api/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/config-persister-file-xml-adapter/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/config-persister-directory-xml-adapter/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/shutdown-api/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/shutdown-impl/${project.version}</bundle>
+ <bundle>mvn:org.osgi/org.osgi.core/${osgi.core.version}</bundle>
+ <bundle>wrap:mvn:com.google.guava/guava/${guava.version}</bundle>
+ <bundle>mvn:org.javassist/javassist/${javassist.version}</bundle>
+ </feature>
</features>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
-<!-- vi: set et smarttab sw=4 tabstop=4: -->
<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>
</dependency>
<!-- scope is compile so all features (there is only one) are installed
into startup.properties and the feature repo itself is not installed -->
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>extras-features</artifactId>
+ <version>${project.version}</version>
+ <type>kar</type>
+ <scope>runtime</scope>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-features</artifactId>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-manager</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-util</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-netconf-connector</artifactId>
<groupId>org.opendaylight.controller.model</groupId>
<artifactId>model-flow-base</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.model</groupId>
- <artifactId>model-flow-management</artifactId>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller.model</groupId>
<artifactId>model-flow-service</artifactId>
lisp.mappingOverwrite = true
# Enable the Solicit-Map-Request (SMR) mechanism
lisp.smr = false
+
+#RESTConf websocket listen port (default is 8181)
+restconf.websocket.port=8181
<appender-ref ref="opendaylight.log"/>
</logger>
+ <!-- BGPCEP plugin -->
+ <logger name="org.opendaylight.protocol" level="INFO"/>
+ <logger name="org.opendaylight.bgpcep" level="INFO"/>
+
<!-- To debug MD-SAL schema loading issues, uncomment this -->
<!--logger name="org.opendaylight.yangtools.yang.parser.impl.util.URLSchemaContextResolver" level="DEBUG"/>
<logger name="org.opendaylight.yangtools.sal.binding.generator.impl.RuntimeGeneratedMappingServiceImpl" level="TRACE"/-->
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.port.rev130925;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.port.rev130925.CommonPort.PortNumber;
+
+
+public class PortNumberBuilder {
+
+ public static PortNumber getDefaultInstance(java.lang.String defaultValue) {
+ try {
+ long uint32 = Long.parseLong(defaultValue);
+ return new PortNumber(uint32);
+ } catch(NumberFormatException e){
+ return new PortNumber(defaultValue);
+ }
+ }
+
+}
+++ /dev/null
-<?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>
- <groupId>org.opendaylight.controller.model</groupId>
- <artifactId>model-parent</artifactId>
- <version>1.1-SNAPSHOT</version>
- </parent>
- <artifactId>model-flow-management</artifactId>
- <packaging>bundle</packaging>
-
- <dependencies>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>model-flow-base</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>model-inventory</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.yangtools.model</groupId>
- <artifactId>opendaylight-l2-types</artifactId>
- </dependency>
- </dependencies>
- <scm>
- <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
- <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
- <tag>HEAD</tag>
- <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
- </scm>
-</project>
+++ /dev/null
-module flow-management {
- namespace "urn:opendaylight:flow:config";
- prefix flow-cfg;
-
- import opendaylight-inventory {prefix inv;revision-date "2013-08-19";}
- import opendaylight-flow-types {prefix flow;}
-
- revision "2013-08-19" {
- description "Initial revision of flow service";
- }
-
-
- grouping flow-entry {
- leaf node {
- type inv:node-ref;
- }
- uses flow:flow;
- }
-
- container flows {
- list flow {
- key "node id";
-
- leaf id {
- type uint32;
- }
- uses flow-entry;
- }
- }
-}
+++ /dev/null
-module group-management {
- namespace "urn:opendaylight:group:config";
- prefix group-cfg;
-
- import opendaylight-inventory {prefix inv;revision-date "2013-08-19";}
- import opendaylight-group-types {prefix group;}
-
- revision "2013-10-24" {
- description "Initial revision of group service";
- }
-
- grouping group-entry {
- leaf node {
- type inv:node-ref;
- }
- uses group:group;
- }
-
- container groups {
- list group {
- key "id node";
-
- leaf id {
- type uint32;
- }
-
- uses group-entry;
- }
- }
-}
+++ /dev/null
-module meter-management {
- namespace "urn:opendaylight:meter:config";
- prefix meter-cfg;
-
- import opendaylight-inventory {prefix inv;revision-date "2013-08-19";}
- import opendaylight-meter-types {prefix meter;}
-
- revision "2013-10-24" {
- description "Initial revision of meter service";
- }
-
- grouping meter-entry {
- leaf node {
- type inv:node-ref;
- }
- uses meter:meter;
- }
-
- container meters {
- list meter {
- key "id node";
-
- leaf id {
- type uint32;
- }
-
- uses meter-entry;
- }
- }
-}
+++ /dev/null
-module port-management {
- namespace "urn:opendaylight:port:config";
- prefix port-cfg;
-
- import opendaylight-inventory {prefix inv;revision-date "2013-08-19";}
- import opendaylight-port-types {prefix port;}
-
- revision "2013-10-24" {
- description "Initial revision of port service";
- }
-
- grouping port-entry {
- leaf node {
- type inv:node-ref;
- }
- uses port:port-mod;
- }
-
- container ports {
- list port {
- key "id node";
-
- leaf id {
- type uint32;
- }
-
- uses port-entry;
- }
- }
-}
+++ /dev/null
-module queue-management {
- namespace "urn:opendaylight:queue:config";
- prefix queue-cfg;
-
- import opendaylight-inventory {prefix inv;revision-date "2013-08-19";}
-
- import opendaylight-queue-types {prefix queue; revision-date "2013-09-25";}
-
-
- revision "2013-10-24" {
- description "Initial revision of queue service";
- }
-
- grouping queue-entry {
- leaf node {
- type inv:node-connector-ref;
-
- }
- uses queue:queue-config-request;
- }
-
- container queues {
- list queue {
- key "id node";
-
- leaf id {
- type uint32;
- }
-
- uses queue-entry;
- }
- }
-}
+++ /dev/null
-module table-management {
- namespace "urn:opendaylight:table:config";
- prefix table-cfg;
-
- import opendaylight-inventory {prefix inv;revision-date "2013-08-19";}
- import opendaylight-table-types {prefix table;}
-
- revision "2013-10-24" {
- description "Initial revision of table service";
- }
-
- grouping table-entry {
-
- leaf node {
- type inv:node-ref;
- }
-
- uses table:table-features;
- }
-
- container tables {
- list table {
- key "id node";
-
- leaf id {
- type uint32;
- }
-
- uses table-entry;
- }
- }
-}
<module>model-flow-base</module>
<module>model-flow-service</module>
<module>model-flow-statistics</module>
- <module>model-flow-management</module>
<module>model-topology</module>
</modules>
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
/**
- *
- * Defines the component of controller and supplies additional metadata. A
- * component of the controller or application supplies a concrete implementation
- * of this interface.
- *
- * A user-implemented component (application) which facilitates the SAL and SAL
- * services to access infrastructure services or providers' functionality.
- *
- *
- *
- */
+*
+* A developer implemented component that gets registered with the Broker.
+*
+* Semantically, a consumer may:
+*
+* <ol>
+* <li>Subscribe for Notifications </li>
+* <li>Invoke RPCs</li>
+* <li>Read from either the operational or config data tree</li>
+* <li>Write to the config data tree</li>
+* </ol>
+* If you need to:
+* <ol>
+* <li> Emit Notifications</li>
+* <li> Provide the implementation of RPCs </li>
+* <li> Write to the operational data tree </li>
+* </ol>
+*
+* Consider using a BindingAwareProvider
+*
+* Examples:
+*
+* To get a NotificationService:
+*
+* {code
+* public void onSessionInitiated(ProviderContext session) {
+* NotificationProviderService notificationService = session.getSALService(NotificationProviderService.class);
+* notificationService.publish(notification)
+* }
+* where notification is an instance of a modeled Notification.
+* For more information on sending notifications via the NotificationProviderService
+* @see org.opendaylight.controller.sal.binding.api.NotificationProviderService
+*
+*
+* A consumer can *invoke* and RPC ( ie, call foo(fooArgs)) but it cannot register an RPC
+* implementation with the MD-SAL that others can invoke(call).
+* To get an invokable RPC:
+*
+* {code
+* public void onSessionInitiated(ProviderContext session) {
+* MyService rpcFlowSalService = session.getRpcService(MyService.class);
+* }
+*
+* Where MyService.class is a Service interface generated from a yang model with RPCs modeled in it. The returned
+* rpcFlowSalService can be used like any other object by invoking its methods. Note, nothing special needs to be done
+* for RoutedRPCs. They just work.
+*
+* To get a DataBroker to allow access to the data tree:
+*
+* {code
+* public void onSessionInitiated(final ProviderContext session) {
+* DataBroker databroker = session.getSALService(BindingDataBroker.class);
+* }
+* }
+* @see org.opendaylight.controller.md.sal.common.api.data.BindingDataBroker
+* for more info on using the DataBroker.
+*
+*/
public interface BindingAwareConsumer {
/**
/**
*
- * Defines the component of controller and supplies additional metadata. A
- * component of the controller or application supplies a concrete implementation
- * of this interface.
+ * A developer implemented component that gets registered with the Broker.
*
+ * Semantically, a provider may:
+ *
+ * <ol>
+ * <li> Emit Notifications</li>
+ * <li> Provide the implementation of RPCs </li>
+ * <li> Write to the operational data tree </li>
+ * </ol>
+ *
+ * If a class is not doing at least one of those three, consider using
+ * a BindingAwareConsumer instead:
+ * @see org.opendaylight.controller.sal.binding.api.BindingAwareConsumer
+ *
+ * <p>
+ *
+ *In addition, a BindingAwareProvider can in pursuit of its goals:
+ *
+ * <ol>
+ * <li>Subscribe for Notifications </li>
+ * <li>Invoke RPCs</li>
+ * <li>Read from either the operational or config data tree</li>
+ * <li>Write to the config data tree</li>
+ * </ol>
+ * (All of the above are things a Consumer can also do).
+ *
+ *<p>
+ *
+ * Examples:
+ *
+ *<p>
+ *
+ * To get a NotificationService:
+ *
+ * {code
+ * public void onSessionInitiated(ProviderContext session) {
+ * NotificationProviderService notificationService = session.getSALService(NotificationProviderService.class);
+ * }
+ * For more information on sending notifications via the NotificationProviderService
+ * @see org.opendaylight.controller.sal.binding.api.NotificationProviderService
+ *
+ * To register an RPC implementation:
+ *
+ * {code
+ * public void onSessionInitiated(ProviderContext session) {
+ * RpcRegistration<MyService> registration = session.addRpcImplementation(MyService.class, myImplementationInstance);
+ * }
+ *
+ * <p>
+ *
+ * Where MyService.class is a Service interface generated from a yang model with RPCs modeled in it and myImplementationInstance
+ * is an instance of a class that implements MyService.
+ *
+ * To register a Routed RPC Implementation:
+ * {code
+ * public void onSessionInitiated(ProviderContext session) {
+ * RoutedRpcRegistration<SalFlowService> flowRegistration = session.addRoutedRpcImplementation(SalFlowService.class, salFlowServiceImplementationInstance);
+ flowRegistration.registerPath(NodeContext.class, nodeInstanceId);
+ * }
+ * }
+ *
+ * Where SalFlowService.class is a Service interface generated from a yang model with RPCs modeled in it and salFlowServiceImplementationInstance is an instance
+ * of a class that implements SalFlowService.
* <p>
- * A user-implemented component (application) which facilitates the SAL and SAL
- * services to access infrastructure services and to provide functionality to
- * {@link Consumer}s and other providers.
+ * The line:
+ * {code
+ * flowRegistration.registerPath(NodeContext.class, nodeInstanceId);
+ * }
+ * Is indicating that the RPC implementation is registered to handle RPC invocations that have their NodeContext pointing to the node with instance id nodeInstanceId.
+ * This bears a bit of further explanation. RoutedRPCs can be 'routed' to an implementation based upon 'context'. 'context' is a pointer (instanceId) to some place
+ * in the data tree. In this example, the 'context' is a pointer to a Node. In this way, a provider can register its ability to provide a service for a particular
+ * Node, but not *all* Nodes. The Broker routes the RPC by 'context' to the correct implementation, without the caller having to do extra work. Because of this when
+ * a RoutedRPC is registered, it needs to also be able to indicate for which 'contexts' it is providing an implementation.
+ *
+ * An example of a Routed RPC would be an updateFlow(node, flow) that would be routed based on node to the provider which had registered to provide
+ * it *for that node*.
+ *
+ *<p>
*
+ * To get a DataBroker to allow access to the data tree:
+ *
+ * {code
+ * public void onSessionInitiated(final ProviderContext session) {
+ * DataBroker databroker = session.getSALService(BindingDataBroker.class);
+ * }
+ * }
+ * @see org.opendaylight.controller.md.sal.common.api.data.BindingDataBroker
+ * for more info on using the DataBroker.
*
*/
public interface BindingAwareProvider {
/**
- * Returns a set of provided implementations of YANG modules and their rpcs.
+ * @deprecated
*
+ * This interface was originally intended to solve problems of how to get Implementations
+ * of functionality from a provider, but that is no longer necessary because the Provider
+ * Registers RPCs in onSessionInitiated.
*
- * @return Set of provided implementation of YANG modules and their Rpcs
+ * Recommend:
+ * {code
+ * public Collection<? extends RpcService> getImplementations() {
+ * return Collections.emptySet();
+ * }
+ * }
*/
+ @Deprecated
Collection<? extends RpcService> getImplementations();
/**
- * Gets a set of implementations of provider functionality to be registered
- * into system during the provider registration to the SAL.
+ * @deprecated
*
- * <p>
- * This method is invoked by {@link Broker#registerProvider(Provider)} to
- * learn the initial provided functionality
+ * This interface was originally intended to solve problems of how to get Functionality
+ * a provider could provide, but that is no longer necessary because the Provider
+ * Registers RPCs in onSessionInitiated.
+ *
+ * Recommend:
+ * {code
+ * public Collection<? extends ProviderFunctionality> getFunctionality() {
+ * return Collections.emptySet();
+ * }
+ * }
*
- * @return Set of provider's functionality.
*/
+ @Deprecated
Collection<? extends ProviderFunctionality> getFunctionality();
/**
*
*
*/
+ @Deprecated
public interface ProviderFunctionality {
}
-
+ /**
+ * Callback signaling initialization of the consumer session to the SAL.
+ *
+ * The consumer MUST use the session for all communication with SAL or
+ * retrieving SAL infrastructure services.
+ *
+ * This method is invoked by
+ * {@link BindingAwareBroker#registerProvider(BindingAwareProvider)}
+ *
+ * @param session Unique session between consumer and SAL.
+ */
void onSessionInitiated(ProviderContext session);
+ /*
+ * @deprecated
+ *
+ * A provider was at one point considered an extension of a consumer, thus this
+ * call. It is deprecated and the @see org.opendaylight.controller.sal.binding.api.BindingAwareConsumer#onSessionInitiated
+ * used, or you should simply use {@link #onSessionInitiated(ProviderContext)}
+ *
+ * Recommend:
+ * {code
+ * public final void onSessionInitialized(ConsumerContext session) {
+ * // NOOP - as method is deprecated
+ * }
+ * }
+ */
+ @Deprecated
void onSessionInitialized(ConsumerContext session);
}
mavenBundle("io.netty", "netty-transport").versionAsInProject(), //
mavenBundle(CONTROLLER, "config-manager").versionAsInProject(), // //
+ mavenBundle(CONTROLLER, "config-util").versionAsInProject(), // //
mavenBundle("commons-io", "commons-io").versionAsInProject(), //
mavenBundle(CONTROLLER, "config-manager").versionAsInProject(), //
mavenBundle(CONTROLLER, "yang-jmx-generator").versionAsInProject(), //
* This initial event will contain all preexisting data as created.
*
* <p>
- * <b>Note</b> that this method may be invoked from a shared thread pool, so
- * implementations SHOULD NOT perform CPU-intensive operations and they
- * definitely MUST NOT invoke any potentially blocking operations.
+ * <b>Note</b>: This method may be invoked from a shared thread pool.
+ * <li>Implementations <b>SHOULD NOT</b> perform CPU-intensive operations on the calling thread.
+ * <li>Implementations <b>MUST NOT block the calling thread</b> - to do so could lead to deadlock
+ * scenarios.
+ *
+ *<br>
*
* @param change
* Data Change Event being delivered.
private static CompositeNode toNotification(final NetconfMessage message, final SchemaContext ctx) {
final Set<NotificationDefinition> notifications = ctx.getNotifications();
final Document document = message.getDocument();
- return XmlDocumentUtils.notificationToDomNodes(document, Optional.fromNullable(notifications));
+ return XmlDocumentUtils.notificationToDomNodes(document, Optional.fromNullable(notifications), ctx);
}
@Override
--- /dev/null
+package org.opendaylight.controller.sal.connect.netconf;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.opendaylight.controller.sal.connect.netconf.schema.mapping.NetconfMessageTransformer;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangContextParser;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.w3c.dom.Document;
+
+/**
+ * @author Lukas Sedlak <lsedlak@cisco.com>
+ */
+public class NetconfToNotificationTest {
+
+ NetconfMessageTransformer messageTransformer;
+
+ NetconfMessage userNotification;
+
+ @Before
+ public void setup() throws Exception {
+ final List<InputStream> modelsToParse = Collections.singletonList(getClass().getResourceAsStream("/schemas/user-notification.yang"));
+ final YangContextParser parser = new YangParserImpl();
+ final Set<Module> modules = parser.parseYangModelsFromStreams(modelsToParse);
+ assertTrue(!modules.isEmpty());
+ final SchemaContext schemaContext = parser.resolveSchemaContext(modules);
+ assertNotNull(schemaContext);
+
+ messageTransformer = new NetconfMessageTransformer();
+ messageTransformer.onGlobalContextUpdated(schemaContext);
+
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ InputStream notifyPayloadStream = getClass().getResourceAsStream("/notification-payload.xml");
+ assertNotNull(notifyPayloadStream);
+
+ final Document doc = XmlUtil.readXmlToDocument(notifyPayloadStream);
+ assertNotNull(doc);
+ userNotification = new NetconfMessage(doc);
+ }
+
+ @Test
+ public void test() throws Exception {
+ final CompositeNode root = messageTransformer.toNotification(userNotification);
+
+ assertNotNull(root);
+ assertEquals(6, root.size());
+ assertEquals("user-visited-page", root.getKey().getLocalName());
+ }
+}
--- /dev/null
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+<eventTime>2014-07-08T11:20:48UTC</eventTime>
+<user-visited-page xmlns="org:opendaylight:notification:test:ns:yang:user-notification">
+ <ui:incoming-user xmlns:ui="org:opendaylight:notification:test:ns:yang:user-notification">ui:public-user</ui:incoming-user>
+ <ip-address>172.23.29.104</ip-address>
+ <mac>00:11:00:ff:dd:02</mac>
+ <browser-id>Chrome 35.0.1916.153 m</browser-id>
+ <region>
+ <name>Slovakia</name>
+ <time-zone>UTC/GMT+2</time-zone>
+ </region>
+ <visiting-date>2014-07-08 11:20:48</visiting-date>
+</user-visited-page>
+</notification>
\ No newline at end of file
--- /dev/null
+module user-notification {
+ yang-version 1;
+ namespace "org:opendaylight:notification:test:ns:yang:user-notification";
+ prefix "user";
+
+ organization "Cisco Systems";
+ contact "Lukas Sedlak";
+ description "Test model for testing notifications";
+
+ revision "2014-07-08" {
+ description "Initial revision";
+ }
+
+ identity user-identity {
+ description "Identity of user incoming to Web Page";
+ }
+
+ identity public-user {
+ base user-identity;
+ description "Identity of random public non-registered user";
+ }
+
+ notification user-visited-page {
+ leaf incoming-user {
+ type identityref {
+ base "user-identity";
+ }
+ }
+
+ leaf ip-address {
+ type string;
+ }
+
+ leaf mac {
+ type string;
+ }
+
+ leaf browser-id {
+ type string;
+ }
+
+ container region {
+ leaf name {
+ type string;
+ }
+
+ leaf time-zone {
+ type string;
+ }
+ }
+
+ leaf visiting-date {
+ type string;
+ }
+ }
+}
\ No newline at end of file
@Override
public void start(BundleContext context) throws Exception {
+ String websocketPortStr = context.getProperty(WebSocketServer.WEBSOCKET_SERVER_CONFIG_PROPERTY);
+ int websocketPort = (websocketPortStr != null && !"".equals(websocketPortStr))
+ ? Integer.parseInt(websocketPortStr) : WebSocketServer.DEFAULT_PORT;
bundleContext = context;
- brokerServiceTrancker = new ServiceTracker<>(context, Broker.class, this);
- brokerServiceTrancker.open();
- webSocketServerThread = new Thread(new WebSocketServer());
+ webSocketServerThread = new Thread(WebSocketServer.createInstance(websocketPort));
webSocketServerThread.setName("Web socket server");
webSocketServerThread.start();
+ brokerServiceTrancker = new ServiceTracker<>(context, Broker.class, this);
+ brokerServiceTrancker.open();
}
@Override
broker.registerToListenDataChanges(listener);
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
- UriBuilder port = uriBuilder.port(WebSocketServer.PORT);
+ UriBuilder port = uriBuilder.port(WebSocketServer.getInstance().getPort());
final URI uriToWebsocketServer = port.replacePath(streamName).build();
return Response.status(Status.OK).location(uriToWebsocketServer).build();
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Preconditions;
+
/**
- * {@link WebSocketServer} is responsible to start and stop web socket server at
- * {@link #PORT}.
+ * {@link WebSocketServer} is responsible to start and stop web socket server
*/
public class WebSocketServer implements Runnable {
private static final Logger logger = LoggerFactory
.getLogger(WebSocketServer.class);
-
- public static final int PORT = 8181;
+ public static final String WEBSOCKET_SERVER_CONFIG_PROPERTY = "restconf.websocket.port";
+ public static final int DEFAULT_PORT = 8181;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
+ private static WebSocketServer singleton = null;
+ private int port = DEFAULT_PORT;
+
+ private WebSocketServer(int port) {
+ this.port = port;
+ }
+
+ /**
+ * Create instance of {@link WebSocketServer}
+ * @param port TCP port used for this server
+ * @return instance of {@link WebSocketServer}
+ */
+ public static WebSocketServer createInstance(int port) {
+ if(singleton != null) {
+ throw new IllegalStateException("createInstance() has already been called");
+ }
+ if(port < 1024) {
+ throw new IllegalArgumentException("Privileged port (below 1024) is not allowed");
+ }
+ singleton = new WebSocketServer(port);
+ return singleton;
+ }
+
+ /**
+ * Return websocket TCP port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Get instance of {@link WebSocketServer} created by {@link #createInstance(int)}
+ * @return instance of {@link WebSocketServer}
+ */
+ public static WebSocketServer getInstance() {
+ Preconditions.checkNotNull(singleton,
+ "createInstance() must be called prior to getInstance()");
+ return singleton;
+ }
+
+ /**
+ * Destroy this already created instance
+ */
+ public static void destroyInstance() {
+ if(singleton == null) {
+ throw new IllegalStateException(
+ "createInstance() must be called prior to destroyInstance()");
+ }
+ getInstance().stop();
+ }
@Override
public void run() {
.channel(NioServerSocketChannel.class)
.childHandler(new WebSocketServerInitializer());
- Channel ch = b.bind(PORT).sync().channel();
- logger.info("Web socket server started at port {}.", PORT);
+ Channel ch = b.bind(port).sync().channel();
+ logger.info("Web socket server started at port {}.", port);
ch.closeFuture().sync();
} catch (InterruptedException e) {
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal-binding-api</artifactId>
*/
package org.opendaylight.controller.config.yang.config.kitchen_service.impl;
+import java.util.concurrent.Future;
+
import org.opendaylight.controller.sample.kitchen.api.EggsType;
import org.opendaylight.controller.sample.kitchen.api.KitchenService;
import org.opendaylight.controller.sample.kitchen.impl.KitchenServiceImpl;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.yang.binding.NotificationListener;
+import org.opendaylight.yangtools.yang.common.RpcResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
}
@Override
- public boolean makeBreakfast( final EggsType eggs, final Class<? extends ToastType> toast, final int toastDoneness ) {
+ public Future<RpcResult<Void>> makeBreakfast( final EggsType eggs,
+ final Class<? extends ToastType> toast,
+ final int toastDoneness ) {
return kitchenService.makeBreakfast( eggs, toast, toastDoneness );
}
}
package org.opendaylight.controller.sample.kitchen.api;
+import java.util.concurrent.Future;
+
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToastType;
+import org.opendaylight.yangtools.yang.common.RpcResult;
public interface KitchenService {
- boolean makeBreakfast( EggsType eggs, Class<? extends ToastType> toast, int toastDoneness );
+ Future<RpcResult<Void>> makeBreakfast( EggsType eggs, Class<? extends ToastType> toast,
+ int toastDoneness );
}
package org.opendaylight.controller.sample.kitchen.impl;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import org.opendaylight.controller.config.yang.config.kitchen_service.impl.KitchenServiceRuntimeMXBean;
+import org.opendaylight.controller.sal.common.util.RpcErrors;
+import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.controller.sample.kitchen.api.EggsType;
import org.opendaylight.controller.sample.kitchen.api.KitchenService;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInputBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToastType;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterListener;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.WheatBread;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.JdkFutureAdapters;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
public class KitchenServiceImpl implements KitchenService, KitchenServiceRuntimeMXBean, ToasterListener {
private static final Logger log = LoggerFactory.getLogger( KitchenServiceImpl.class );
private final ToasterService toaster;
+ private final ListeningExecutorService executor =
+ MoreExecutors.listeningDecorator( Executors.newCachedThreadPool() );
+
private volatile boolean toasterOutOfBread;
public KitchenServiceImpl(ToasterService toaster) {
}
@Override
- public boolean makeBreakfast( EggsType eggs, Class<? extends ToastType> toast, int toastDoneness ) {
+ public Future<RpcResult<Void>> makeBreakfast( EggsType eggsType, Class<? extends ToastType> toastType,
+ int toastDoneness ) {
+
+ // Call makeToast and use JdkFutureAdapters to convert the Future to a ListenableFuture,
+ // The OpendaylightToaster impl already returns a ListenableFuture so the conversion is
+ // actually a no-op.
+
+ ListenableFuture<RpcResult<Void>> makeToastFuture = JdkFutureAdapters.listenInPoolThread(
+ makeToast( toastType, toastDoneness ), executor );
+
+ ListenableFuture<RpcResult<Void>> makeEggsFuture = makeEggs( eggsType );
+
+ // Combine the 2 ListenableFutures into 1 containing a list of RpcResults.
+
+ ListenableFuture<List<RpcResult<Void>>> combinedFutures =
+ Futures.allAsList( ImmutableList.of( makeToastFuture, makeEggsFuture ) );
+
+ // Then transform the RpcResults into 1.
+
+ return Futures.transform( combinedFutures,
+ new AsyncFunction<List<RpcResult<Void>>,RpcResult<Void>>() {
+ @Override
+ public ListenableFuture<RpcResult<Void>> apply( List<RpcResult<Void>> results )
+ throws Exception {
+ boolean atLeastOneSucceeded = false;
+ Builder<RpcError> errorList = ImmutableList.builder();
+ for( RpcResult<Void> result: results ) {
+ if( result.isSuccessful() ) {
+ atLeastOneSucceeded = true;
+ }
+
+ if( result.getErrors() != null ) {
+ errorList.addAll( result.getErrors() );
+ }
+ }
+
+ return Futures.immediateFuture(
+ Rpcs.<Void> getRpcResult( atLeastOneSucceeded, errorList.build() ) );
+ }
+ } );
+ }
+
+ private ListenableFuture<RpcResult<Void>> makeEggs( EggsType eggsType ) {
+
+ return executor.submit( new Callable<RpcResult<Void>>() {
+
+ @Override
+ public RpcResult<Void> call() throws Exception {
+
+ // We don't actually do anything here - just return a successful result.
+ return Rpcs.<Void> getRpcResult( true, Collections.<RpcError>emptyList() );
+ }
+ } );
+ }
+
+ private Future<RpcResult<Void>> makeToast( Class<? extends ToastType> toastType,
+ int toastDoneness ) {
if( toasterOutOfBread )
{
log.info( "We're out of toast but we can make eggs" );
- return true;
+ return Futures.immediateFuture( Rpcs.<Void> getRpcResult( true,
+ Arrays.asList( RpcErrors.getRpcError( "", "partial-operation", null,
+ ErrorSeverity.WARNING,
+ "Toaster is out of bread but we can make you eggs",
+ ErrorType.APPLICATION, null ) ) ) );
}
// Access the ToasterService to make the toast.
- // We don't actually make the eggs for this example - sorry.
- MakeToastInputBuilder toastInput = new MakeToastInputBuilder();
- toastInput.setToasterDoneness( (long) toastDoneness);
- toastInput.setToasterToastType( toast );
- try {
- RpcResult<Void> result = toaster.makeToast( toastInput.build() ).get();
+ MakeToastInput toastInput = new MakeToastInputBuilder()
+ .setToasterDoneness( (long) toastDoneness )
+ .setToasterToastType( toastType )
+ .build();
+
+ return toaster.makeToast( toastInput );
+ }
+ @Override
+ public Boolean makeScrambledWithWheat() {
+ try {
+ // This call has to block since we must return a result to the JMX client.
+ RpcResult<Void> result = makeBreakfast( EggsType.SCRAMBLED, WheatBread.class, 2 ).get();
if( result.isSuccessful() ) {
- log.info( "makeToast succeeded" );
+ log.info( "makeBreakfast succeeded" );
} else {
- log.warn( "makeToast failed: " + result.getErrors() );
+ log.warn( "makeBreakfast failed: " + result.getErrors() );
}
return result.isSuccessful();
+
} catch( InterruptedException | ExecutionException e ) {
- log.warn( "Error occurred during toast creation" );
+ log.warn( "An error occurred while maing breakfast: " + e );
}
- return false;
- }
- @Override
- public Boolean makeScrambledWithWheat() {
- return makeBreakfast( EggsType.SCRAMBLED, WheatBread.class, 2 );
+ return Boolean.FALSE;
}
/**
long toastsMade = (long) platformMBeanServer.getAttribute(providerOn, "ToastsMade");
assertEquals(0, toastsMade);
- boolean toasts = true;
+ boolean success = true;
// Make toasts using OSGi service
- toasts &= kitchenService.makeBreakfast( EggsType.SCRAMBLED, HashBrown.class, 4);
- toasts &= kitchenService.makeBreakfast( EggsType.POACHED, WhiteBread.class, 8 );
+ success &= kitchenService.makeBreakfast( EggsType.SCRAMBLED, HashBrown.class, 4).get().isSuccessful();
+ success &= kitchenService.makeBreakfast( EggsType.POACHED, WhiteBread.class, 8 ).get().isSuccessful();
- Assert.assertTrue("Not all toasts done by " + kitchenService, toasts);
+ Assert.assertTrue("Not all breakfasts succeeded", success);
// Verify toasts made count on provider via JMX/config-subsystem
toastsMade = (long) platformMBeanServer.getAttribute(providerOn, "ToastsMade");
<logger name="org.opendaylight.yangtools.yang.parser" level="ERROR"/>
- <root level="debug">
+ <logger name="org.opendaylight.controller.sample.toaster.provider" level="DEBUG"/>
+
+ <root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
import org.opendaylight.controller.sal.common.util.RpcErrors;
import org.opendaylight.controller.sal.common.util.Rpcs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
public class OpendaylightToaster implements ToasterService, ToasterProviderRuntimeMXBean,
DataChangeListener, AutoCloseable {
private final ExecutorService executor;
- // As you will see we are using multiple threads here. Therefore we need to be careful about concurrency.
- // In this case we use the taskLock to provide synchronization for the current task.
- private volatile Future<RpcResult<Void>> currentTask;
- private final Object taskLock = new Object();
+ // The following holds the Future for the current make toast task.
+ // This is used to cancel the current toast.
+ private final AtomicReference<Future<?>> currentMakeToastTask = new AtomicReference<>();
private final AtomicLong amountOfBreadInStock = new AtomicLong( 100 );
public void setDataProvider(final DataBroker salDataProvider) {
this.dataProvider = salDataProvider;
- updateStatus();
+ setToasterStatusUp( null );
}
/**
if (dataProvider != null) {
WriteTransaction t = dataProvider.newWriteOnlyTransaction();
t.delete(LogicalDatastoreType.OPERATIONAL,TOASTER_IID);
- t.commit().get(); // FIXME: This call should not be blocking.
+ ListenableFuture<RpcResult<TransactionStatus>> future = t.commit();
+ Futures.addCallback( future, new FutureCallback<RpcResult<TransactionStatus>>() {
+ @Override
+ public void onSuccess( RpcResult<TransactionStatus> result ) {
+ LOG.debug( "Delete Toaster commit result: " + result );
+ }
+
+ @Override
+ public void onFailure( Throwable t ) {
+ LOG.error( "Delete of Toaster failed", t );
+ }
+ } );
}
}
- private Toaster buildToaster() {
- // We don't need to synchronize on currentTask here b/c it's declared volatile and
- // we're just doing a read.
- boolean isUp = currentTask == null;
+ private Toaster buildToaster( ToasterStatus status ) {
// note - we are simulating a device whose manufacture and model are
// fixed (embedded) into the hardware.
// This is why the manufacture and model number are hardcoded.
- ToasterBuilder tb = new ToasterBuilder();
- tb.setToasterManufacturer(TOASTER_MANUFACTURER).setToasterModelNumber(TOASTER_MODEL_NUMBER)
- .setToasterStatus(isUp ? ToasterStatus.Up : ToasterStatus.Down);
- return tb.build();
+ return new ToasterBuilder().setToasterManufacturer( TOASTER_MANUFACTURER )
+ .setToasterModelNumber( TOASTER_MODEL_NUMBER )
+ .setToasterStatus( status )
+ .build();
}
/**
}
/**
- * RestConf RPC call implemented from the ToasterService interface.
+ * RPC call implemented from the ToasterService interface that cancels the current
+ * toast, if any.
*/
@Override
public Future<RpcResult<Void>> cancelToast() {
- synchronized (taskLock) {
- if (currentTask != null) {
- currentTask.cancel(true);
- currentTask = null;
- }
+
+ Future<?> current = currentMakeToastTask.getAndSet( null );
+ if( current != null ) {
+ current.cancel( true );
}
+
// Always return success from the cancel toast call.
- return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError> emptySet()));
+ return Futures.immediateFuture( Rpcs.<Void> getRpcResult( true,
+ Collections.<RpcError>emptyList() ) );
}
/**
- * RestConf RPC call implemented from the ToasterService interface.
+ * RPC call implemented from the ToasterService interface that attempts to make toast.
*/
@Override
public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
LOG.info("makeToast: " + input);
- synchronized (taskLock) {
- if (currentTask != null) {
- // return an error since we are already toasting some toast.
- LOG.info( "Toaster is already making toast" );
+ final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
- RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
- RpcErrors.getRpcError( "", "in-use", null, ErrorSeverity.WARNING,
- "Toaster is busy", ErrorType.APPLICATION, null ) ) );
- return Futures.immediateFuture(result);
- }
- else if( outOfBread() ) {
- RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
- RpcErrors.getRpcError( "out-of-stock", "resource-denied", null, null,
- "Toaster is out of bread",
- ErrorType.APPLICATION, null ) ) );
- return Futures.immediateFuture(result);
- }
- else {
- // Notice that we are moving the actual call to another thread,
- // allowing this thread to return immediately.
- // The MD-SAL design encourages asynchronus programming. If the
- // caller needs to block until the call is
- // complete then they can leverage the blocking methods on the
- // Future interface.
- currentTask = executor.submit(new MakeToastTask(input));
+ checkStatusAndMakeToast( input, futureResult );
+
+ return futureResult;
+ }
+
+ private List<RpcError> makeToasterOutOfBreadError() {
+ return Arrays.asList(
+ RpcErrors.getRpcError( "out-of-stock", "resource-denied", null, null,
+ "Toaster is out of bread",
+ ErrorType.APPLICATION, null ) );
+ }
+
+ private List<RpcError> makeToasterInUseError() {
+ return Arrays.asList(
+ RpcErrors.getRpcError( "", "in-use", null, ErrorSeverity.WARNING,
+ "Toaster is busy", ErrorType.APPLICATION, null ) );
+ }
+
+ private void checkStatusAndMakeToast( final MakeToastInput input,
+ final SettableFuture<RpcResult<Void>> futureResult ) {
+
+ // Read the ToasterStatus and, if currently Up, try to write the status to Down.
+ // If that succeeds, then we essentially have an exclusive lock and can proceed
+ // to make toast.
+
+ final ReadWriteTransaction tx = dataProvider.newReadWriteTransaction();
+ ListenableFuture<Optional<DataObject>> readFuture =
+ tx.read( LogicalDatastoreType.OPERATIONAL, TOASTER_IID );
+
+ final ListenableFuture<RpcResult<TransactionStatus>> commitFuture =
+ Futures.transform( readFuture, new AsyncFunction<Optional<DataObject>,
+ RpcResult<TransactionStatus>>() {
+
+ @Override
+ public ListenableFuture<RpcResult<TransactionStatus>> apply(
+ Optional<DataObject> toasterData ) throws Exception {
+
+ ToasterStatus toasterStatus = ToasterStatus.Up;
+ if( toasterData.isPresent() ) {
+ toasterStatus = ((Toaster)toasterData.get()).getToasterStatus();
+ }
+
+ LOG.debug( "Read toaster status: {}", toasterStatus );
+
+ if( toasterStatus == ToasterStatus.Up ) {
+
+ if( outOfBread() ) {
+ LOG.debug( "Toaster is out of bread" );
+
+ return Futures.immediateFuture( Rpcs.<TransactionStatus>getRpcResult(
+ false, null, makeToasterOutOfBreadError() ) );
+ }
+
+ LOG.debug( "Setting Toaster status to Down" );
+
+ // We're not currently making toast - try to update the status to Down
+ // to indicate we're going to make toast. This acts as a lock to prevent
+ // concurrent toasting.
+ tx.put( LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
+ buildToaster( ToasterStatus.Down ) );
+ return tx.commit();
+ }
+
+ LOG.debug( "Oops - already making toast!" );
+
+ // Return an error since we are already making toast. This will get
+ // propagated to the commitFuture below which will interpret the null
+ // TransactionStatus in the RpcResult as an error condition.
+ return Futures.immediateFuture( Rpcs.<TransactionStatus>getRpcResult(
+ false, null, makeToasterInUseError() ) );
+ }
+ } );
+
+ Futures.addCallback( commitFuture, new FutureCallback<RpcResult<TransactionStatus>>() {
+ @Override
+ public void onSuccess( RpcResult<TransactionStatus> result ) {
+ if( result.getResult() == TransactionStatus.COMMITED ) {
+
+ // OK to make toast
+ currentMakeToastTask.set( executor.submit(
+ new MakeToastTask( input, futureResult ) ) );
+ } else {
+
+ LOG.debug( "Setting error result" );
+
+ // Either the transaction failed to commit for some reason or, more likely,
+ // the read above returned ToasterStatus.Down. Either way, fail the
+ // futureResult and copy the errors.
+
+ futureResult.set( Rpcs.<Void>getRpcResult( false, null, result.getErrors() ) );
+ }
}
- }
- updateStatus();
- return currentTask;
+ @Override
+ public void onFailure( Throwable ex ) {
+ if( ex instanceof OptimisticLockFailedException ) {
+
+ // Another thread is likely trying to make toast simultaneously and updated the
+ // status before us. Try reading the status again - if another make toast is
+ // now in progress, we should get ToasterStatus.Down and fail.
+
+ LOG.debug( "Got OptimisticLockFailedException - trying again" );
+
+ checkStatusAndMakeToast( input, futureResult );
+
+ } else {
+
+ LOG.error( "Failed to commit Toaster status", ex );
+
+ // Got some unexpected error so fail.
+ futureResult.set( Rpcs.<Void> getRpcResult( false, null, Arrays.asList(
+ RpcErrors.getRpcError( null, null, null, ErrorSeverity.ERROR,
+ ex.getMessage(),
+ ErrorType.APPLICATION, ex ) ) ) );
+ }
+ }
+ } );
}
/**
public Future<RpcResult<java.lang.Void>> restockToaster(final RestockToasterInput input) {
LOG.info( "restockToaster: " + input );
- synchronized( taskLock ) {
- amountOfBreadInStock.set( input.getAmountOfBreadToStock() );
+ amountOfBreadInStock.set( input.getAmountOfBreadToStock() );
- if( amountOfBreadInStock.get() > 0 ) {
- ToasterRestocked reStockedNotification =
- new ToasterRestockedBuilder().setAmountOfBread( input.getAmountOfBreadToStock() ).build();
- notificationProvider.publish( reStockedNotification );
- }
+ if( amountOfBreadInStock.get() > 0 ) {
+ ToasterRestocked reStockedNotification = new ToasterRestockedBuilder()
+ .setAmountOfBread( input.getAmountOfBreadToStock() ).build();
+ notificationProvider.publish( reStockedNotification );
}
- return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError> emptySet()));
+ return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError>emptyList()));
}
/**
return toastsMade.get();
}
- private void updateStatus() {
- if (dataProvider != null) {
- WriteTransaction tx = dataProvider.newWriteOnlyTransaction();
- tx.put(LogicalDatastoreType.OPERATIONAL,TOASTER_IID, buildToaster());
+ private void setToasterStatusUp( final Function<Boolean,Void> resultCallback ) {
- try {
- tx.commit().get();
- } catch (InterruptedException | ExecutionException e) {
- LOG.warn("Failed to update toaster status, operational otherwise", e);
+ WriteTransaction tx = dataProvider.newWriteOnlyTransaction();
+ tx.put( LogicalDatastoreType.OPERATIONAL,TOASTER_IID, buildToaster( ToasterStatus.Up ) );
+
+ ListenableFuture<RpcResult<TransactionStatus>> commitFuture = tx.commit();
+
+ Futures.addCallback( commitFuture, new FutureCallback<RpcResult<TransactionStatus>>() {
+ @Override
+ public void onSuccess( RpcResult<TransactionStatus> result ) {
+ if( result.getResult() != TransactionStatus.COMMITED ) {
+ LOG.error( "Failed to update toaster status: " + result.getErrors() );
+ }
+
+ notifyCallback( result.getResult() == TransactionStatus.COMMITED );
}
- } else {
- LOG.trace("No data provider configured, not updating status");
- }
+
+ @Override
+ public void onFailure( Throwable t ) {
+ // We shouldn't get an OptimisticLockFailedException (or any ex) as no
+ // other component should be updating the operational state.
+ LOG.error( "Failed to update toaster status", t );
+
+ notifyCallback( false );
+ }
+
+ void notifyCallback( boolean result ) {
+ if( resultCallback != null ) {
+ resultCallback.apply( result );
+ }
+ }
+ } );
}
private boolean outOfBread()
return amountOfBreadInStock.get() == 0;
}
- private class MakeToastTask implements Callable<RpcResult<Void>> {
+ private class MakeToastTask implements Callable<Void> {
final MakeToastInput toastRequest;
+ final SettableFuture<RpcResult<Void>> futureResult;
- public MakeToastTask(final MakeToastInput toast) {
- toastRequest = toast;
+ public MakeToastTask( final MakeToastInput toastRequest,
+ final SettableFuture<RpcResult<Void>> futureResult ) {
+ this.toastRequest = toastRequest;
+ this.futureResult = futureResult;
}
@Override
- public RpcResult<Void> call() {
+ public Void call() {
try
{
- // make toast just sleeps for n secondn per doneness level.
+ // make toast just sleeps for n seconds per doneness level.
long darknessFactor = OpendaylightToaster.this.darknessFactor.get();
Thread.sleep(darknessFactor * toastRequest.getToasterDoneness());
notificationProvider.publish( new ToasterOutOfBreadBuilder().build() );
}
- synchronized (taskLock) {
- currentTask = null;
- }
+ // Set the Toaster status back to up - this essentially releases the toasting lock.
+ // We can't clear the current toast task nor set the Future result until the
+ // update has been committed so we pass a callback to be notified on completion.
+
+ setToasterStatusUp( new Function<Boolean,Void>() {
+ @Override
+ public Void apply( Boolean result ) {
+
+ currentMakeToastTask.set( null );
+
+ LOG.debug("Toast done");
- updateStatus();
+ futureResult.set( Rpcs.<Void>getRpcResult( true, null,
+ Collections.<RpcError>emptyList() ) );
- LOG.debug("Toast done");
+ return null;
+ }
+ } );
- return Rpcs.<Void> getRpcResult(true, null, Collections.<RpcError> emptySet());
+ return null;
}
}
}
org.opendaylight.controller.netconf.confignetconfconnector.transactions,
org.opendaylight.controller.netconf.confignetconfconnector.util,
org.opendaylight.controller.netconf.confignetconfconnector.osgi,
- org.opendaylight.controller.config.util,
org.opendaylight.controller.netconf.confignetconfconnector.exception,</Private-Package>
<Import-Package>com.google.common.base,
com.google.common.collect,
com.google.common.io,
org.opendaylight.yangtools.yang.model.api.type,
org.opendaylight.yangtools.sal.binding.generator.spi,
- org.opendaylight.yangtools.sal.binding.yang.types,</Import-Package>
+ org.opendaylight.yangtools.sal.binding.yang.types,
+ org.opendaylight.controller.config.util
+ </Import-Package>
<Export-Package></Export-Package>
</instructions>
</configuration>
import javax.xml.bind.annotation.XmlRootElement;
import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService;
+import org.opendaylight.controller.northbound.commons.exception.GenericExceptionMapper;
import org.opendaylight.controller.northbound.commons.query.QueryContextProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
_singletons.add(getJsonProvider());
_singletons.add(new JacksonJsonProcessingExceptionMapper());
_singletons.add(new QueryContextProvider());
+ _singletons.add(new GenericExceptionMapper());
}
////////////////////////////////////////////////////////////////
--- /dev/null
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+public class GenericExceptionMapper implements ExceptionMapper<Exception> {
+
+ @Override
+ public Response toResponse(Exception exception) {
+ //check if WebApplicationException and reuse status code
+ if (exception instanceof WebApplicationException) {
+ WebApplicationException ex = (WebApplicationException) exception;
+ return Response.status(ex.getResponse().getStatus()).
+ entity(ex.getResponse().getEntity()).build();
+ }
+ // throw 500 for all other errors
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).
+ entity(exception.getMessage()).build();
+ }
+
+}
<module>features/base</module>
<module>features/controller</module>
<module>features/adsal</module>
+ <module>features/extras</module>
<module>opendaylight/dummy-console</module>
<module>opendaylight/karaf-branding</module>
<module>opendaylight/distribution/opendaylight-karaf</module>