Opendaylight课堂之深度剖析toaster(三)

上一篇内容

五、使用服务--mytoaster-consumer

与官方wiki两点区别

1、Odlwiki中采用直接定义java api的方式实现的comsumer,这里采用yang模型定义。因此在实际开发中用的比较多还是yang来定义模型。

2、Wiki中还是采用jmx当作客户端,为了简便(主要引入jmx太复杂,而且本人也不是很了解jmx,哈哈大实话)不使用jmx,采用postmanrestconf当作客户端。如果有能力自己可以使用node.js搭建一个最简单的web server,直接调用接口以及实现监听。

目录结构如下:

[root@localhost mytoaster-consumer]# tree

├── api

   ├── pom.xml

   ├── src

       └── main

           └── yang

               └── mytoaster-consumer.yang

├── impl

   ├── pom.xml

   └── src

       └──main

           ├──java

             └── org.opendaylight.controller.demo2.mytoaster.myconsumer.impl

                  └──KitchenServiceImpl.java

           └──resources

└── pom.xml

[root@localhost mytoaster-consumer]#

mytaoster-consumer.yang文件内容如下(参考odl wikijava代码写成yang模型):

module mytoaster-consumer {
    yang-version 1;
    namespace
      "http://mytoaster.org/toaster/consumer";
    prefix consumer;
        organization "Netconf Central";
    contact
      "Andy Bierman <andy@netconfcentral.org>";
    description
      "YANG version of the TOASTER-MIB.";
    import mytoaster {
        prefix mytoaster;
    }
    revision "2017-10-14" {
      description
        "Toaster module in progress.";
    }
    typedef EggType {
        type enumeration {
            enum SCRAMBLED;
            enum OVER_EASY;
            enum POACHED;
        }
    }
    rpc makeBreakfast {
        input {
            leaf eggType {
                type EggType;
            }
            leaf toastType {
                type identityref {
                    base mytoaster:mytoast-type;
                }
            }
            leaf toastDoneness {
                type uint32;
            }
        }
        output {
            leaf info {
                type string;
            }
        }
    }
}

mytoaster-consumer目录下面pom.xml文件,主要内容(可以参考之前pom进行修改):

<groupId>org.opendaylight.controller.demo2</groupId>
<artifactId>mytoaster-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
  <module>api</module>
  <module>impl</module>
</modules>

api目录下面pom.xml文件,主要内容(可以参考之前pom进行修改):

<groupId>org.opendaylight.controller.demo2</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mytoaster</artifactId>
<packaging>bundle</packaging>

Impl目录下面pom.xml文件,主要内容(可以参考之前pom进行修改):

<groupId>org.opendaylight.controller.demo2</groupId>
<artifactId>mytoaster-consumer-impl</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>bundle</packaging>

<dependency>
  <groupId>${project.groupId}</groupId>
  <artifactId>mytoaster</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>${project.groupId}</groupId>
  <artifactId>mytoaster-consumer-api</artifactId>
  <version>${project.version}</version>
</dependency>

public class KitchenServiceImpl implements MytoasterListener, MytoasterConsumerService {
    private final MytoasterService mytoaster;
    private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
            Executors.newCachedThreadPool());

    public KitchenServiceImpl(MytoasterService mytoaster) {
        this.mytoaster = mytoaster;
    }

    private Future<RpcResult<MakeToastOutput>>  makeToast(Long doness, java.lang.Class<? extends org.opendaylight.yang.gen.v1.http.mytoaster.org.toaster.rev171014.MytoastType>  type) {
        MakeToastInputBuilder builder = new MakeToastInputBuilder();
        builder.setToasterDoneness(doness);
        builder.setToasterToastType(type);
        return mytoaster.makeToast(builder.build());
    }

    @Override
    public Future<RpcResult<MakeBreakfastOutput>> makeBreakfast(MakeBreakfastInput input) {
        //制作面包
        ListenableFuture<RpcResult<MakeToastOutput>> makeToastFuture = JdkFutureAdapters.listenInPoolThread(
                makeToast(input.getToastDoneness(), input.getToastType()), executor);
        //制作鸡蛋
        ListenableFuture<RpcResult<MakeToastOutput>> makeEggsFuture = new EggOutput().makeEggs(input.getEggType());

        /* 将两个future合并 然后统一监测结果 */
        ListenableFuture<List<RpcResult<MakeToastOutput>>> combinedFutures  = Futures
                .allAsList(ImmutableList.of(makeToastFuture, makeEggsFuture));

        return Futures.transform(combinedFutures, new AsyncFunction<List<RpcResult<MakeToastOutput>>, RpcResult<MakeBreakfastOutput>>() {
            @Override
            public ListenableFuture<RpcResult<MakeBreakfastOutput>> apply(List<RpcResult<MakeToastOutput>> rpcResults) throws Exception {
                boolean atLeastOneSucceeded = true;
                ImmutableList.Builder<RpcError> errorList = ImmutableList.builder();
                for (RpcResult<MakeToastOutput> result :rpcResults) {
                    if (!result.isSuccessful()) {
                        atLeastOneSucceeded = false;
                    }
                    if (result.getErrors() != null) {
                        errorList.addAll(result.getErrors());
                    }
                }
                MakeBreakfastOutputBuilder builder = new MakeBreakfastOutputBuilder();
                if (atLeastOneSucceeded) {
                    builder.setInfo("Make Breakfast Completed.");
                } else {
                    builder.setInfo("Make Breakfast UnCompleted.");
                }
                return Futures.immediateFuture(RpcResultBuilder.success(builder.build()).build());
            }
        });
    }

    /* 包装一下 为了统一处理*/
    private class EggOutput {
        //鸡蛋采用异步通知方式
        public ListenableFuture<RpcResult<MakeToastOutput>> makeEggs(EggType eggType) {
            return KitchenServiceImpl.this.executor.submit(new Callable<RpcResult<MakeToastOutput>>() {
                @Override
                public RpcResult<MakeToastOutput> call() throws Exception {
                    MakeToastOutputBuilder builder = new MakeToastOutputBuilder();
                    builder.setMakeInfo("Wait a memont for Eggs");
                    return RpcResultBuilder.success(builder.build()).build();
                }
            });
        }
    }
}

实现了具体代码之后,我们还需要设置blueprint以及添加feature定义:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
           odl:use-default-for-reference-types="true">
    <!-- 由于需要制作面包 因此导入rpc服务 -->
    <odl:rpc-service id="MyToasterService" interface="org.opendaylight.yang.gen.v1.http.mytoaster.org.toaster.rev171024.MytoasterService"/>
    <!-- 定义一个KitchenServiceImpl类 -->
    <bean id="kitchenService" class="org.opendaylight.controller.demo2.mytoaster.myconsumer.impl.KitchenServiceImpl">
        <argument ref="MyToasterService"/>
    </bean>
    <service ref="kitchenService" interface="org.opendaylight.yang.gen.v1.http.mytoaster.org.toaster.consumer.rev171014.MytoasterConsumerService"
             odl:type="default"/>
    <odl:rpc-implementation ref="kitchenService" />
</blueprint>

mdsal/features-mdsal目录中pom文件增加:

<dependency>
  <groupId>org.opendaylight.controller.demo2</groupId>
  <artifactId>mytoaster-consumer-api</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.opendaylight.controller.demo2</groupId>
  <artifactId>mytoaster-consumer-impl</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

mdsal/features-mdsal/src/main/features/features.xml增加对comsumer bundle的定义:

<bundle>mvn:org.opendaylight.controller.demo2/mytoaster-consumer-api/{{VERSION}}</bundle>
<bundle>mvn:org.opendaylight.controller.demo2/mytoaster-consumer-impl/{{VERSION}}</bundle>

然后进行编译即可,下图是测试toaster



URL:http://localhost:8080/restconf/operations/mytoaster-consumer:makeBreakfast
数据:{
  "input": {
    "toastType": "mytoaster:white-bread",
    "toastDoneness": "5",
    "eggType": "SCRAMBLED"
  }
}

六、实现Notification

在现实生活中烤面包是异步的,例如,人不需要一直守在面包机前面,当烤完面包之后,面包机会发出嘀嘀..”声响。若实现这种方式,则需要用到notification了。

我们需要改造makeBreakFast方法:收到rpc请求后,将制作鸡蛋和制作面包这两个任务提交给线程池处理,然后立即响应rpc请求。等面包和鸡蛋制作完成后以notification方式上报给请求者(odl官方wiki中没有实现这种方式。),后面实现这种实现。

现实生活中烤面机不可能无休止的能制作出面包,它也是有原材料限制的,因此还是需要设置MyopendaylightToaster.java

/* wiki中采用NotificationProviderService 此类即将被废弃 */
private NotificationPublishService notificationProvider;/* 用于发送notification消息 */
private final AtomicLong amountOfBreadInStock = new AtomicLong(100);/* 面包数量 默认可以烤100个面包 */
public void setNotificationProvider(NotificationPublishService notificationProvider) {
    this.notificationProvider = notificationProvider;
}
private void checkStatusAndMakeToast(final MakeToastInput input, final int tries,
                                        final SettableFuture<RpcResult<MakeToastOutput>> futureResult) {
  ...
if (status == Mytoaster.ToasterStatus.Idle) {
    if(outOfBread()) {//表示面包已经用完 无法再烤面包
        return Futures.immediateFailedCheckedFuture(
                new TransactionCommitFailedException("",
                        RpcResultBuilder.newError(RpcError.ErrorType.APPLICATION,
                                "Warning", "Out Bread")));
    }
    tx.put(LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
            createToaster(Mytoaster.ToasterStatus.Work));
    return tx.submit();
}
...
}
private boolean outOfBread() {
    return amountOfBreadInStock.get() == 0;
}
@Override
public Future<RpcResult<Void>> restockToaster(RestockToasterInput input) {
    amountOfBreadInStock.set(input.getAmountOfBreadToStock());

    if (amountOfBreadInStock.get() > 0) {//表示已经添加面包 可以继续烤
        ToasterRestocked reStockedNotification = new ToasterRestockedBuilder()
                .setAmountOfBread(input.getAmountOfBreadToStock()).build();
        notificationProvider.offerNotification(reStockedNotification);
    }

    return Futures.immediateFuture(RpcResultBuilder.<Void>success().build());
}
private class MakeToastTask implements Callable<Void> {
    ....
@Override
    public Void call() throws Exception {
        try {
            amountOfBreadInStock.getAndDecrement();//表示消耗一个面包
            // 睡眠15秒 表示制作面包
            Thread.sleep(1000*15);
            //表示面包已经制作完成
            notificationProvider.offerNotification(new ToasterOutOfBreadBuilder().build());
            System.out.println("Break is out!!Please take it");        
        } catch (InterruptedException e) {
   ...
   }
}

代码写完后需要在blueprint中增加notification服务,修改prodiver下面的blueprint文件:

<reference id="notification" interface="org.opendaylight.controller.md.sal.binding.api.NotificationPublishService"/>
<bean ...>
<property name="notificationProvider" ref="notification"/>
</bean>

以上功能只是完成notification消息分发,但是接收消息还没有处理,在cosumer中进行消息处理:

public class KitchenServiceImpl implements MytoasterListener, MytoasterConsumerService {
    ...
    @Override
    public void onToasterRestocked(ToasterRestocked notification) {
        System.out.println("ToasterOutOfBread notification");
    }
    @Override
    public void onToasterOutOfBread(ToasterOutOfBread notification) {
        //终端显示:面包完成
        System.out.println("ToasterRestocked notification - amountOfBread: " + notification.getMessage() );
    }
}

consumerblueprint需要进行notification注册,只有注册之后,才能接收到消息:

<odl:notification-listener ref="kitchenService" />

好了整个notification机制已经完成,测试一下:

通过postman下发制作早餐,rpc立即响应回来了,但是下面的打印,在15秒之后打印出来的。说明我们已经能异步接收消息了。




扩展内容:如果了解netconf协议的同学,都知道netconf中有一个种功能时订阅。也就是说订阅某种数据(对于netconf来说应该时tree\node,当数据发生变化的时候会发给订阅者。代码中consumer也相当于是一个订阅者。这里在介绍一种,通过websockect方式订阅。

通过websocket订阅必须有以下三步骤:

1、查询发布通知(Postman-Get:http://127.0.0.1:8181/restconf/18/data/ietf-restconf-monitoring:restconf-state/streams

返回内容
{
    "streams": {
        "stream": [
            ....
            {
                "name": "toasterOutOfBread",
                "replay-support": true,
                "access": [
                    {
                        "encoding": "XML",
                        "location": "ws://127.0.0.1:8185/create-notification-stream/mytoaster:toasterOutOfBread"
                    },
                    {
                        "encoding": "JSON",
                        "location": "ws://127.0.0.1:8185/create-notification-stream/mytoaster:toasterOutOfBread/JSON"
                    }
                ],
                "description": "Indicates that the toaster has run of out bread."
            },          
            {
                "name": "toasterRestocked",
                "replay-support": true,
                "access": [
                    {
                        "encoding": "XML",
                        "location": "ws://127.0.0.1:8185/create-notification-stream/mytoaster:toasterRestocked"
                    },
                    {
                        "encoding": "JSON",
                        "location": "ws://127.0.0.1:8185/create-notification-stream/mytoaster:toasterRestocked/JSON"
                    }
                ],
                "description": "Indicates that the toaster has run of out bread."
            },
            ....
        ]
    }
}

2、订阅某个通知(Postman-Get)http://localhost:8181/restconf/18/data/ietf-restconf-monitoring:restconf-state/streams/stream=toasterOutOfBread/access=JSON/location

注意:1)蓝色替换成第一步中查询回来的name名字,也就是notifiaction名字2)订阅支持xmljson,默认是xml

返回内容
{
    "location": "ws://127.0.0.1:8185/create-notification-stream/mytoaster:toasterOutOfBread/JSON"
}

3、通过rest-client进行websocket连接(Postman支持websocket):

url地址是第二步返回内容, websocket建立成功,再次通过postman下发rpc则在rest-cleint中显示notification信息:


七、系统启动时自动安装feature

demo在实现过程中,总是在karaf启动之后,手动安装feature,这样很是麻烦。如果在karaf启动之后feature自动安装呢?目前有两种方式:

方法1:直接修改opendaylight-karaf/target/assembly/etc/org.apache.karaf.features.cfg文件,featuresBoot配置项中增加自定义的feature。但是这种方式存在两个问题:

1)每次编译版本之后文件又被修改成原来的。

2)在我们发布产品A的时候,可能需要Feature-F,但是发布产品B的时候又不需要Feature-F,那么如何解决这种问题呢?因此有方法2

方法2:在同事和网友的指点下,在插件karaf-maven-plugin中有一个配置项,可以支持在启动的时候去修改。修改karaf/opendaylight-karaf/pom.xml,增加如下内容:

<profiles>
  <profile>
    <id>build-mytoaster</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.karaf.tooling</groupId>
          <artifactId>karaf-maven-plugin</artifactId>
          <configuration>
            <bootFeatures>
              <bootFeature>odl-restconf-all</bootFeature>
              <bootFeature>odl-mytoaster</bootFeature>
            </bootFeatures>
          </configuration>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

 上面这种方式,增加了一个profile,然后指定bootFeature就可以完成自动安装,同时又可支持不同OEM,可谓一举两得。在编译时增加一个编译参数-Pbuild-mytoaster,具体如下:mvn clean install -DskipTests -Dskip.karaf.featureTest=true -Dmaven.test.skip=true -Dcheckstyle.skip=true  -Dmaven.javadoc.skip=true-Pbuild-mytoaster 

启动完成之后可以查看,feature是否已经安装成功。

总结:

这次文章写的内容比较多,前前后后写了有两周。自从搞odl开始,toaster这个demo前前后后写了有三四遍,每写一次都会加深对odl的理解,这次总结也算是对odl总结,希望通过这篇文章,能狗帮助更多人,同时感谢我的同时以及帮助我的网友。

关于toaster所有源代码,下载地址








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值