OpenDaylight系列文章(六):我的第一个ODL工程(下篇)


在上篇中介绍了怎样生成一个简单的组件(工程),但在真正的工程开发中,需要能够容纳多个自定义组件(工程)的工程。下面通过创建一个demo工程(上篇topology工程的代码迁移到demo工程中,成为demo的一个子工程),为大家介绍下如何创建带有子工程的工程。

1. 工程创建

1.1. 生成demo工程
mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=org.opendaylight.controller -DarchetypeArtifactId=opendaylight-startup-archetype -DarchetypeRepository=https://nexus.opendaylight.org/content/repositories/public -DarchetypeVersion=1.3.2-Carbon -DgroupId=org.opendaylight.demo -DartifactId=demo -DclassPrefix=Demo -Dcopyright=ZebraDecoder


1.2. 生成topology子工程
进入demo文件夹,然后进行执行此步骤:
mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=org.opendaylight.controller -DarchetypeArtifactId=opendaylight-startup-archetype -DarchetypeRepository=https://nexus.opendaylight.org/content/repositories/public -DarchetypeVersion=1.3.2-Carbon -DgroupId=org.opendaylight.demo -DartifactId=topology -DclassPrefix=Topology -Dcopyright=ZebraDecoder


demo目录如下:

1.png


1.3. 删除demo文件夹下的部分目录和文件
删除和此工程无关的目录和文件(无关目录和文件是指本工程不需要使用的目录和文件,并不是说这些目录和文件没有作用,删除的目的仅仅是为了简化配置的修改,提高读者构建工程的成功率),例如:demo工程的作用仅仅是组织子工程,不需要api、impl等文件。
rm -r api cli deploy-site.xml impl it src

1.4. 修改demo/pom.xml
modules标签内容为:
<modules>
<module>karaf</module>
<module>features</module>
<module>artifacts</module>
<module>topology</module>
</modules>


1.5. 修改demo/artifacts/pom.xml
dependencyManagement标签内容:
<dependencyManagement>
<dependencies>
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>demo-features</artifactId>
    <version>${project.version}</version>
    <classifier>features</classifier>
    <type>xml</type>
  </dependency>
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>topology-api</artifactId>
    <version>${project.version}</version>
  </dependency>
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>topology-impl</artifactId>
    <version>${project.version}</version>
  </dependency>
</dependencies>
</dependencyManagement>


1.6. 修改demo/features/pom.xml
删除dependencies标签下的demo-api和demo-impl依赖,增加topology-api和topology-impl依赖:
<dependency>
  <groupId>${project.groupId}</groupId>
  <artifactId>topology-impl</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>${project.groupId}</groupId>
  <artifactId>topology-api</artifactId>
  <version>${project.version}</version>
</dependency>


1.7. 修改demo/features/src/main/features/features.xml
删除所有的feature标签,添加topology子工程的feature
<feature name='odl-demo-topology' version='${project.version}'
    description='Odl :: Demo :: Topology'>
<bundle>mvn:org.opendaylight.demo/topology-api/{{VERSION}}</bundle>
<bundle>mvn:org.opendaylight.demo/topology-impl/{{VERSION}}</bundle>
</feature>


1.8. 删除demo/topology目录下部分目录和文件

2.png


1.9. 修改demo/topology/pom.xml
modules标签如下
<modules>
<module>api</module>
<module>impl</module>
</modules>


1.10. 同步上篇中关于topology的yang
替换topology.yang,并在demo/topology/api/pom.xml 添加相应的依赖:

<dependencies>
    <dependency>
        <groupId>org.opendaylight.mdsal.model</groupId>
        <artifactId>ietf-inet-types-2013-07-15</artifactId>
        <version>1.2.2-Carbon</version>
    </dependency>
</dependencies>

1.11. 同步上篇中关于topology的代码
/org/opendaylight/demo/impl/TopologyProvider.java(注意该文件的包名变为org.opendaylight.demo.impl),并在demo/topology/impl/pom.xml 添加相应的依赖:
<dependency>
    <groupId>org.opendaylight.mdsal.model</groupId>
    <artifactId>ietf-topology</artifactId>
    <version>2013.10.21.10.2-Carbon</version>
</dependency>
<dependency>
    <groupId>org.opendaylight.controller.model</groupId>
    <artifactId>model-inventory</artifactId>
    <version>1.5.2-Carbon</version>
</dependency>
<dependency>
    <groupId>org.opendaylight.openflowplugin.model</groupId>
    <artifactId>model-flow-service</artifactId>
    <version>0.4.2-Carbon</version>
</dependency>


1.12. 修改impl-blueprint.xml
在demo/topology/impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml添加
<odl:rpc-implementation ref="provider"/>


1.13. 修改features文件夹中pom.xml以及features.xml
修改demo/features/pom.xml,在properties标签中添加:
<openflowplugin.version>0.4.2-Carbon</openflowplugin.version>
<l2switch.version>0.5.2-Carbon</l2switch.version>
demo/features/src/main/features/features.xml修改如下:
<repository>
  mvn:org.opendaylight.openflowplugin/features-openflowplugin/${openflowplugin.version}/xml/features
</repository>
<repository>
  mvn:org.opendaylight.l2switch/features-l2switch/${l2switch.version}/xml/features
</repository>
<feature name='odl-demo-topology' version='${project.version}'
    description='Odl :: Demo :: Topology'>
    <feature version='${mdsal.model.version}'>odl-mdsal-models</feature>
    <feature version='${mdsal.version}'>odl-mdsal-broker</feature>
    <feature version="${openflowplugin.version}">odl-openflowplugin-flow-services</feature>
    <bundle>mvn:org.opendaylight.demo/topology-api/{{VERSION}}</bundle>
    <bundle>mvn:org.opendaylight.demo/topology-impl/{{VERSION}}</bundle>
</feature>
</features>


1.14. 编译启动
cd demo
mvn clean install -Dcheckstyle.skip=true -DskipTests -Dmaven.javadoc.skip=true
./karaf/target/assembly/bin/karaf


3.png


从上图中可以看到,odl-demo-topology并没有自动安装上去,需要自己安装:

4.png


至此, demo工程创建完成,后面文章的代码开发都基于demo工程。

2. 工程应用

在上篇中我们了解了RPC,这一篇我们通过Notification和DataStoreChange两种应用带大家了解下子工程之间的交互。
2.1. 何为Notifications
In YANG, Notifications represent asynchronous events, published by providers for listeners。
这是OpenDayLight官网给Notifications的定义,即Notifications为异步事件。Notifications是一种发布订阅/模式(Publish/Subscribe)。发布者发布通知,接收者接收通知,从而实现消息的交互功能。
下面我们用实例来解释Notifications的发送和监听。
2.1.1. Notifications发送

第一步:定义触发Notifications的RPC或者事件
本例我们定义RPC参数变化触发Notifications。关于RPC的详情请看上篇,这里不赘述。

rpc add-link-song {
input {
    leaf link-id {
        type inet:uri;
        description "an identifier for link";
    }
    leaf link-song {
        type string;
    }
}
output {
      leaf error-code {
        type uint32;
     }
       leaf error-info {
         type string;
      }
}
}

第二步:定义Notifications的YANG Model

notification link-status-changed {
leaf link-id {
    type inet:uri;
    description "an identifier for link";
}
leaf link-song {
    type string;
}
}

编译YANG model后会生成两个接口LinkStatusChanged.java和TopologyListener.java。
LinkStatusChanged.java是注入通告内容使用的;TopologyListener.java是订阅通告的bundle在收到通告时,实现这个接口完成事务的。
第三步:在impl文件下的impl-blueprint.xml文件中注册
简单介绍下blueprint, 它的设计是用来为OSGi容器提供依赖注入的框架。
Blueprint.xml文档,有四种主要元素:
 Bean:用来描述创建Java实例的元素,可以指定实例初始化的类名,构造方法,构造方法的入参及属性;
 service:把bean发布为OSGi services;
 Reference:通过接口名引用一个OSGi services,可以指定一个特定的属性过滤器;
 reference-list:通过接口名引用多个 OSGi services ,可以指定一个特定的属性过滤器;
此例的关于Notifications的注册如下:
<reference id="notificationService"
       interface="org.opendaylight.controller.sal.binding.api.NotificationProviderService" />
<bean id="provider"
class="org.opendaylight.demo.impl.TopologyProvider"
init-method="init" destroy-method="close">
<argument ref="dataBroker" />
<argument ref="notificationService"/>
</bean>


第四步:添加link song触发Notifications的发布
RPC代码具体实现见附件,此处仅列出关于Notifications的代码。
LinkStatusChanged notification = new LinkStatusChangedBuilder()
    .setLinkId(linkUri)
    .setLinkSong(input.getLinkSong())
    .build();
LOG.info("before notification is {}", notification);
notifs.publish(notification);
LOG.info("after notification is {}", notification);


2.1.2. 订阅Notification
新建monitor-feature,此处不再赘述,步骤同工程创建中的1.2,1.8,1.9。此feature负责监听上述的notifications,然后完成相应的实现。
第一步:在monitor-impl的pom文件增加依赖:
monitor-impl要监听topology-api发布的notifications,注册依赖如下:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>topology-api</artifactId>
<version>${project.version}</version>
</dependency>


第二步:在monitor-impl的impl-blueprint.xml文件中增加注册
<reference id="notificationProviderService"
       interface="org.opendaylight.controller.sal.binding.api.NotificationProviderService" />

<bean id="provider" class="org.opendaylight.demo.impl.MonitorProvider"
init-method="init" destroy-method="close">
<argument ref="dataBroker" />
<argument ref="notificationProviderService" />
</bean>


第三步:在MonitorProvider.java的init方法中注册类LinkListenHandler.java

public void init() {
this.listenerRegistration = notificationProviderService.registerNotificationListener(new LinkListenHandler());
LOG.info("MonitorProvider Session Initiated");
}

第四步:类LinkListenHandler.java实现TopologyListener.java接口

public class LinkListenHandler implements TopologyListener {
private static final Logger LOG = LoggerFactory.getLogger(LinkListenHandler.class);
@Override
public void onLinkStatusChanged(LinkStatusChanged notification) {
    LOG.info("OH! B-I-N-G-O! B-I-N-G-O! B-I-N-G-O!");
}
}

到此为止,监听完毕。
2.1.3. 查看结果

第一步:启动控制器,修改link的权重信息,触发notification事件

5.png


第二步:RPC返回成功,打印第一个LOG信息,说明Notifications发送成功。
2018-04-04 16:31:14,237 | INFO  | qtp754089476-115 | AddLinkSong                      | 317 - org.opendaylight.demo.topology-impl - 0.1.0.SNAPSHOT | before notification is LinkStatusChanged [_linkId=Uri [_value=link:1], _linkSong=There was a farmer had a dog, and bingo was his name., augmentation=[]]


第三步:Notifications发送后,monitor模块订阅这个notifications事件,打印出第二个LOG信息,说明订阅成功。
2018-04-04 16:31:14,239 | INFO  | pool-16-thread-1 | LinkListenHandler                | 319 - org.opendaylight.demo.monitor-impl - 0.1.0.SNAPSHOT | OH! B-I-N-G-O! B-I-N-G-O! B-I-N-G-O!


2.2. 何为DataStoreChange
MD-SAL中通过DataStore以树型存储数据,通过DataBroker与DataStore进行交互。DataBroker中数据存储的变化会触发onDataTreeChanged ()事件,在onDataTreeChanged ()中响应DataStore的变化。
DataStore有三种监听事件:
 DataChangeListener:只能对整棵树进行监听,树中任何一个叶子节点的变化都触发DataChangeListener的事件。
 DataTreeChangeListener:可以用来监听整棵树或者某个子树。通过DataTreeIdentifier进行数据的精确定位,可以是树干,也可以是更精细的定位。
 DOMDataTreeChangeListener:通过DOMDataBroker访问DataStore,使用Binding Independent方式,通过QName对数据树进行索引和数据的定位,直接获取QName对应的位置变化事件。
本文我们针对DataTreeChangeListener的实现进行具体的分析,DataBroker的WriteTransaction.java接口有三种方法:
 Put
 Merge
 Delete
这三种方法对应到onDataChange()事件的
 WRITE
 SUBTREE_MODIFIED
 DELETE
还是以修改拓扑链路song为例,具体流程如下:
在network-topology的config数据库中增加节点link-song

grouping link-augment {
leaf link-song {
    type string;
}
}

augment "/nt:network-topology/nt:topology/nt:link" {
ext:augment-identifier "extended-link";
uses link-augment;
}

2.2.1. 数据存储

private <D extends DataObject> void merge(final LogicalDatastoreType logicalDatastoreType,
                                      final InstanceIdentifier<D> path, D data) {
final WriteTransaction transaction = dataBroker.newWriteOnlyTransaction();
transaction.merge(logicalDatastoreType, path, data, true);
final CheckedFuture<Void, TransactionCommitFailedException> future = transaction.submit();
try {
    future.checkedGet();
} catch (final TransactionCommitFailedException e) {
    LOG.warn("Failed to merge {} ", path, e);
}
}

具体使用merge还是put,需要看业务需要。例子此处用merge。
当link-song发生变化,采用merge的方式写入network-topology的config数据库中。
2.2.2. 数据监听
monitor模块监听link变化
第一步:在monitor-impl的impl-blueprint.xml文件中增加注册
<bean id="linkChangeListenerImpl"
  class="org.opendaylight.demo.impl.LinkChangeListenerImpl"
  init-method="init">
<argument ref="dataBroker" />
</bean>


第二步:LinkChangeListenerImpl.java类实现接口DataTreeChangeListener.java
public class LinkChangeListenerImpl implements DataTreeChangeListener<Link> {
private static final Logger LOG = LoggerFactory.getLogger(LinkChangeListenerImpl.class);


第三步:指定到监听的具体节点,注册监听

public void init() {
this.registration = this.dataBroker.registerDataTreeChangeListener(
        new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, getLinkIId()), this);
}

第四步:实现接口DataTreeChangeListener.java中的方法

@Override
public void onDataTreeChanged(Collection<DataTreeModification<Link>> changes) {
for (DataTreeModification<Link> change : changes) {
    DataObjectModification<Link> rootNode = change.getRootNode();
    modificationType = rootNode.getModificationType();
    if (Objects.equals(rootNode.getDataAfter(), rootNode.getDataBefore())) {
        return;
    }
    LOG.info("operation type is {}", modificationType);
    switch (modificationType) {
        case WRITE:
            LOG.info("There was a farmer had a dog, and xiao li was his name。");
            break;
        case SUBTREE_MODIFIED:
            LOG.info("There was a farmer had a dog, and mao mao was his name。");
            break;
        case DELETE:
            LOG.info("There was a farmer had a dog, it's no name。");
            break;
        default:
            break;
    }
}
}

至此,数据库监听流程结束。
2.2.3. 查看结果
 第一步:启动控制器,修改link的song信息。
 第二步: 触发监听事件。
处理监听事件,打印LOG信息。因为此例子采用merge的方式写库,监听后应该是走SUBTREE_MODIFIED
分支,LOG打印如下:
2018-04-04 16:32:28,919 | INFO  | on-dispatcher-46 | LinkChangeListenerImpl           | 319 - org.opendaylight.demo.monitor-impl - 0.1.0.SNAPSHOT | operation type is SUBTREE_MODIFIED
2018-04-04 16:32:28,919 | INFO  | on-dispatcher-46 | LinkChangeListenerImpl           | 319 - org.opendaylight.demo.monitor-impl - 0.1.0.SNAPSHOT | There was a farmer had a dog, and mao mao was his name。


总结

到此为止,我的第一个ODL工程创建完成了,麻雀虽小,五脏俱全。在真正的工程中使用,我们还需要更进一步的学习,欢迎大家继续期待我们小组后续的文章,谢谢大家。

1 个评论

最好开个系列文章的专题,把这些收录一下,不然有些沉贴都找不到了

要回复文章请先登录注册